Skip to main content

graphrag_cli/ui/components/
status_bar.rs

1//! Status bar component with color-coded indicators and query mode badge
2
3use crate::{
4    action::{Action, QueryMode, StatusType},
5    theme::Theme,
6    ui::Spinner,
7};
8use ratatui::{
9    layout::{Alignment, Rect},
10    style::{Color, Modifier, Style},
11    text::{Line, Span},
12    widgets::{Block, Borders, Paragraph},
13    Frame,
14};
15
16/// Status bar with indicator and query mode badge
17pub struct StatusBar {
18    message: String,
19    status_type: StatusType,
20    progress_active: bool,
21    progress_message: String,
22    spinner: Spinner,
23    /// Current query mode (displayed as right-aligned badge)
24    query_mode: QueryMode,
25    theme: Theme,
26}
27
28impl StatusBar {
29    pub fn new() -> Self {
30        Self {
31            message: "Ready — use /config to load a configuration".to_string(),
32            status_type: StatusType::Info,
33            progress_active: false,
34            progress_message: String::new(),
35            spinner: Spinner::new(),
36            query_mode: QueryMode::default(),
37            theme: Theme::default(),
38        }
39    }
40
41    pub fn set_status(&mut self, status_type: StatusType, message: String) {
42        self.status_type = status_type;
43        self.message = message;
44        self.progress_active = false;
45    }
46
47    pub fn clear(&mut self) {
48        self.message = "Ready".to_string();
49        self.status_type = StatusType::Info;
50        self.progress_active = false;
51    }
52
53    pub fn start_progress(&mut self, message: String) {
54        self.progress_active = true;
55        self.progress_message = message;
56        self.status_type = StatusType::Progress;
57    }
58
59    pub fn stop_progress(&mut self) {
60        self.progress_active = false;
61        self.progress_message.clear();
62    }
63}
64
65impl super::Component for StatusBar {
66    fn handle_action(&mut self, action: &Action) -> Option<Action> {
67        match action {
68            Action::SetStatus(status_type, message) => {
69                self.set_status(*status_type, message.clone());
70                None
71            },
72            Action::ClearStatus => {
73                self.clear();
74                None
75            },
76            Action::StartProgress(message) => {
77                self.start_progress(message.clone());
78                None
79            },
80            Action::StopProgress => {
81                self.stop_progress();
82                None
83            },
84            Action::SetQueryMode(mode) => {
85                self.query_mode = *mode;
86                None
87            },
88            _ => None,
89        }
90    }
91
92    fn render(&mut self, f: &mut Frame, area: Rect) {
93        let block = Block::default()
94            .borders(Borders::ALL)
95            .border_style(self.theme.border());
96
97        let display_message = if self.progress_active {
98            let spinner_frame = self.spinner.tick();
99            format!(
100                "{} {} {}",
101                spinner_frame,
102                self.status_type.icon(),
103                self.progress_message
104            )
105        } else {
106            format!("{} {}", self.status_type.icon(), self.message)
107        };
108
109        let msg_style = Style::default().fg(self.status_type.color()).add_modifier(
110            if matches!(self.status_type, StatusType::Error | StatusType::Warning) {
111                Modifier::BOLD
112            } else {
113                Modifier::empty()
114            },
115        );
116
117        // Query mode badge color
118        let (mode_color, mode_label) = match self.query_mode {
119            QueryMode::Ask => (Color::DarkGray, " [ASK] "),
120            QueryMode::Explain => (Color::Cyan, " [EXPLAIN] "),
121            QueryMode::Reason => (Color::Magenta, " [REASON] "),
122        };
123
124        let mode_badge = Span::styled(
125            mode_label.to_owned(),
126            Style::default()
127                .fg(Color::Black)
128                .bg(mode_color)
129                .add_modifier(Modifier::BOLD),
130        );
131
132        let hint = Span::styled(
133            " | Ctrl+N next | ↑↓ scroll | ? help | Esc input | Ctrl+C quit".to_owned(),
134            self.theme.dimmed(),
135        );
136
137        let line = Line::from(vec![
138            Span::styled(display_message, msg_style),
139            hint,
140            mode_badge,
141        ]);
142
143        let paragraph = Paragraph::new(line).block(block).alignment(Alignment::Left);
144
145        f.render_widget(paragraph, area);
146    }
147}
148
149impl Default for StatusBar {
150    fn default() -> Self {
151        Self::new()
152    }
153}