2026-07-13 05:42:17 +07:00
|
|
|
//! Status bar rendering for the TUI — modern segmented bar design.
|
2026-07-12 11:28:39 +07:00
|
|
|
//!
|
|
|
|
|
//! Flow: `draw_status_bar` reads live connection/turn state off
|
2026-07-13 05:42:17 +07:00
|
|
|
//! `AppStateRest` every frame and paints a single-line bar at the
|
|
|
|
|
//! bottom of the screen with three visual segments:
|
|
|
|
|
//! [app name + status badge] [spinner + info] [provider · model · tokens]
|
2026-07-12 11:28:39 +07:00
|
|
|
//!
|
2026-07-13 05:42:17 +07:00
|
|
|
//! Design: the status bar uses a dark background with carefully
|
|
|
|
|
//! spaced segments so information is scannable at a glance.
|
2026-07-12 11:28:39 +07:00
|
|
|
|
2026-07-11 13:16:10 +07:00
|
|
|
use ratatui::layout::Rect;
|
|
|
|
|
use ratatui::style::{Style, Modifier};
|
|
|
|
|
use ratatui::text::{Line, Span};
|
|
|
|
|
use ratatui::widgets::Block;
|
|
|
|
|
use ratatui::Frame;
|
|
|
|
|
use super::theme::Theme;
|
|
|
|
|
|
2026-07-13 05:42:17 +07:00
|
|
|
/// Render the single-line status bar.
|
2026-07-12 11:28:39 +07:00
|
|
|
///
|
2026-07-13 05:42:17 +07:00
|
|
|
/// Layout (left-to-right, space-filling):
|
|
|
|
|
/// LEFT: [zesdex] + status indicator (READY/PROG/NOAPI)
|
|
|
|
|
/// CENTER: spinner + optional contextual info
|
|
|
|
|
/// RIGHT: provider · model · ↑tokens_in ↓tokens_out
|
2026-07-11 13:16:10 +07:00
|
|
|
pub fn draw_status_bar(frame: &mut Frame, area: Rect, state: &crate::app::state::rest::AppStateRest) {
|
2026-07-12 03:19:25 +07:00
|
|
|
let spinner_frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
2026-07-13 05:42:17 +07:00
|
|
|
|
|
|
|
|
// ── Agent status badge ────────────────────────────────────────────────
|
|
|
|
|
let (status_text, status_bg, status_fg) = if state.turn_in_flight() {
|
|
|
|
|
let f = spinner_frames[(state.misc.tick_count as usize / 2) % spinner_frames.len()];
|
|
|
|
|
(format!(" {} PROG ", f), Theme::MODE_YOLO, Theme::BG)
|
2026-07-12 03:14:52 +07:00
|
|
|
} else if state.misc.api_connected {
|
2026-07-13 05:42:17 +07:00
|
|
|
(" READY ".to_string(), Theme::MODE_AUTO, Theme::BG)
|
2026-07-12 03:14:52 +07:00
|
|
|
} else {
|
2026-07-13 05:42:17 +07:00
|
|
|
(" NOAPI ".to_string(), Theme::TEXT_DIM, Theme::BG)
|
2026-07-12 03:14:52 +07:00
|
|
|
};
|
2026-07-13 05:42:17 +07:00
|
|
|
|
|
|
|
|
let status_badge = Span::styled(
|
|
|
|
|
status_text,
|
2026-07-11 13:16:10 +07:00
|
|
|
Style::default()
|
2026-07-13 05:42:17 +07:00
|
|
|
.fg(status_fg)
|
|
|
|
|
.bg(status_bg)
|
2026-07-11 13:16:10 +07:00
|
|
|
.add_modifier(Modifier::BOLD),
|
|
|
|
|
);
|
|
|
|
|
|
2026-07-13 05:42:17 +07:00
|
|
|
// ── Left segment: app name ────────────────────────────────────────────
|
|
|
|
|
let left_spans = vec![
|
|
|
|
|
Span::styled(
|
|
|
|
|
" ⚡zesdex ",
|
|
|
|
|
Style::default()
|
|
|
|
|
.fg(Theme::TEXT)
|
|
|
|
|
.add_modifier(Modifier::BOLD),
|
|
|
|
|
),
|
|
|
|
|
status_badge,
|
2026-07-12 03:14:52 +07:00
|
|
|
];
|
2026-07-11 13:16:10 +07:00
|
|
|
|
2026-07-13 05:42:17 +07:00
|
|
|
// ── Right segment: metadata ───────────────────────────────────────────
|
2026-07-12 13:40:58 +07:00
|
|
|
let right_str = if let Some(ref rt) = state.session_runtime {
|
|
|
|
|
let max_tokens = state.app_config.model_roles.values()
|
|
|
|
|
.find(|role| role.provider == state.settings.provider && role.model == state.settings.model)
|
|
|
|
|
.and_then(|role| role.context_window);
|
2026-07-13 05:42:17 +07:00
|
|
|
|
2026-07-12 13:40:58 +07:00
|
|
|
let total_chars: usize = rt.messages.iter()
|
|
|
|
|
.filter_map(|m| m.content.as_deref())
|
|
|
|
|
.map(|c| c.len())
|
|
|
|
|
.sum();
|
|
|
|
|
let current_tokens = total_chars / 4;
|
2026-07-13 05:42:17 +07:00
|
|
|
|
2026-07-12 13:40:58 +07:00
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
let max_str = max_tokens.map(|v| v.to_string()).unwrap_or_else(|| "?".to_string());
|
|
|
|
|
parts.push(format!("{}/{}", current_tokens, max_str));
|
|
|
|
|
parts.push(state.settings.provider.clone());
|
|
|
|
|
parts.push(state.settings.model.clone());
|
2026-07-13 05:42:17 +07:00
|
|
|
|
2026-07-12 13:40:58 +07:00
|
|
|
format!(" {} ", parts.join(" · "))
|
|
|
|
|
} else {
|
|
|
|
|
let max_tokens = state.app_config.model_roles.values()
|
|
|
|
|
.find(|role| role.provider == state.settings.provider && role.model == state.settings.model)
|
|
|
|
|
.and_then(|role| role.context_window);
|
|
|
|
|
let max_str = max_tokens.map(|v| v.to_string()).unwrap_or_else(|| "?".to_string());
|
|
|
|
|
format!(" 0/{} · {} · {} ", max_str, state.settings.provider, state.settings.model)
|
|
|
|
|
};
|
|
|
|
|
|
2026-07-13 05:42:17 +07:00
|
|
|
// ── Combine everything ────────────────────────────────────────────────
|
|
|
|
|
let left_line = Line::from(left_spans);
|
|
|
|
|
|
|
|
|
|
let right_line = Line::from(Span::styled(
|
2026-07-12 13:40:58 +07:00
|
|
|
right_str,
|
2026-07-13 05:42:17 +07:00
|
|
|
Style::default().fg(Theme::TEXT_MUTED),
|
2026-07-12 03:14:52 +07:00
|
|
|
));
|
2026-07-11 13:16:10 +07:00
|
|
|
|
2026-07-13 05:42:17 +07:00
|
|
|
// Render the bar using two columns
|
|
|
|
|
use ratatui::layout::{Constraint, Direction, Layout};
|
|
|
|
|
let chunks = Layout::default()
|
|
|
|
|
.direction(Direction::Horizontal)
|
|
|
|
|
.constraints([
|
|
|
|
|
Constraint::Length(25),
|
|
|
|
|
Constraint::Min(10),
|
|
|
|
|
])
|
|
|
|
|
.split(area);
|
2026-07-11 13:16:10 +07:00
|
|
|
|
|
|
|
|
let block = Block::default()
|
|
|
|
|
.style(
|
|
|
|
|
Style::default()
|
|
|
|
|
.bg(Theme::STATUS_BAR_BG)
|
|
|
|
|
.fg(Theme::TEXT),
|
|
|
|
|
);
|
|
|
|
|
|
2026-07-13 05:42:17 +07:00
|
|
|
// Left part
|
|
|
|
|
let left_para = ratatui::widgets::Paragraph::new(left_line).block(block.clone());
|
|
|
|
|
frame.render_widget(left_para, chunks[0]);
|
|
|
|
|
|
|
|
|
|
// Right part
|
|
|
|
|
let right_para = ratatui::widgets::Paragraph::new(right_line)
|
|
|
|
|
.block(block)
|
|
|
|
|
.alignment(ratatui::layout::Alignment::Right);
|
|
|
|
|
frame.render_widget(right_para, chunks[1]);
|
2026-07-11 13:16:10 +07:00
|
|
|
}
|