1use crate::layout::AppLayout;
5use crate::theme::Theme;
6use crate::widgets;
7
8use super::{App, Panel};
9
10impl App {
11 pub fn draw(&mut self, frame: &mut ratatui::Frame) {
12 let layout = AppLayout::compute(
13 frame.area(),
14 self.show_side_panels,
15 self.desired_input_height(),
16 );
17
18 self.draw_header(frame, layout.header);
19 if self.sessions.current().show_splash {
20 widgets::splash::render(frame, layout.chat);
21 } else {
22 let mut cache = std::mem::take(&mut self.sessions.current_mut().render_cache);
23 let max_scroll = widgets::chat::render(self, frame, layout.chat, &mut cache);
24 self.sessions.current_mut().render_cache = cache;
25 self.sessions.current_mut().scroll_offset =
26 self.sessions.current().scroll_offset.min(max_scroll);
27 }
28 self.draw_side_panel(frame, &layout);
29 let spinner_idx = self.throbber_state().index().cast_unsigned();
30 let busy = self.is_agent_busy();
31 let activity_label = self.status_label().map(str::to_owned);
32 let supervisor_label = self.supervisor_activity_label();
33 let effective_label = activity_label.or(supervisor_label);
34 widgets::input::render(
35 self,
36 frame,
37 layout.input,
38 busy,
39 effective_label.as_deref(),
40 spinner_idx,
41 );
42 widgets::status::render(self, &self.metrics, frame, layout.status);
43
44 if let Some(state) = &self.file_picker_state {
45 widgets::file_picker::render(state, frame, layout.input);
46 }
47
48 if let Some(state) = &self.slash_autocomplete {
49 widgets::slash_autocomplete::render(state, frame, layout.input);
50 }
51
52 if let Some(state) = &self.reverse_search {
53 widgets::reverse_search::render(
54 state,
55 &self.sessions.current().input_history,
56 frame,
57 layout.input,
58 );
59 }
60
61 if let Some(state) = &self.confirm_state {
62 widgets::confirm::render(&state.prompt, frame, frame.area());
63 }
64
65 if let Some(state) = &self.elicitation_state {
66 widgets::elicitation::render(&state.dialog, frame, frame.area());
67 }
68
69 if let Some(palette) = &self.command_palette {
70 widgets::command_palette::render(palette, frame, frame.area());
71 }
72
73 if self.show_help {
74 widgets::help::render(frame, frame.area());
75 }
76 }
77
78 pub(super) fn draw_header(&self, frame: &mut ratatui::Frame, area: ratatui::layout::Rect) {
79 use ratatui::text::{Line, Span};
80 use ratatui::widgets::Paragraph;
81
82 let theme = Theme::default();
83
84 let provider = if self.metrics.provider_name.is_empty() {
85 "---"
86 } else {
87 &self.metrics.provider_name
88 };
89 let model = if self.metrics.model_name.is_empty() {
90 "---"
91 } else {
92 &self.metrics.model_name
93 };
94
95 let ctx_badge = if self.metrics.extended_context {
96 " [1M CTX]"
97 } else {
98 ""
99 };
100 let text = format!(
101 " Zeph v{} | Provider: {provider} | Model: {model}{ctx_badge}",
102 env!("CARGO_PKG_VERSION")
103 );
104
105 let line = Line::from(Span::styled(text, theme.header));
106 let paragraph = Paragraph::new(line).style(theme.header);
107 frame.render_widget(paragraph, area);
108 }
109
110 fn draw_side_panel(&mut self, frame: &mut ratatui::Frame, layout: &AppLayout) {
111 widgets::skills::render(&self.metrics, frame, layout.skills);
112 widgets::memory::render(&self.metrics, frame, layout.memory);
113
114 {
117 use ratatui::layout::{Constraint, Direction, Layout};
118 let context_split = Layout::default()
119 .direction(Direction::Vertical)
120 .constraints([
121 Constraint::Length(3),
122 Constraint::Length(3),
123 Constraint::Min(0),
124 ])
125 .split(layout.resources);
126 widgets::context_gauge::render(&self.metrics, frame, context_split[0]);
127 widgets::compaction_badge::render(&self.metrics, frame, context_split[1]);
128 widgets::resources::render(&self.metrics, frame, context_split[2]);
129 }
130
131 let tick = self.throbber_state.index().cast_unsigned();
132 let has_graph = self.metrics.orchestration_graph.as_ref().is_some_and(|s| {
133 !s.is_stale()
135 });
136 let panel_focused = self.active_panel == Panel::SubAgents;
137
138 if panel_focused {
141 widgets::subagents::render_interactive(
142 &self.metrics,
143 &mut self.subagent_sidebar,
144 frame,
145 layout.subagents,
146 tick,
147 );
148 } else if has_graph && !self.sessions.current().plan_view_active {
149 widgets::plan_view::render(&self.metrics, frame, layout.subagents, tick);
150 } else if self.has_recent_security_events() {
151 widgets::security::render(&self.metrics, frame, layout.subagents);
152 } else {
153 widgets::subagents::render(&self.metrics, frame, layout.subagents);
154 }
155
156 if self.active_panel == Panel::Fleet {
158 widgets::fleet::render(
159 &self.fleet_snapshot,
160 frame,
161 layout.subagents,
162 &mut self.fleet_list_state,
163 );
164 }
165
166 if self.show_task_panel {
168 if self.task_supervisor.is_some() {
169 widgets::task_registry::render(
170 &self.cached_task_snapshots,
171 tick,
172 layout.subagents,
173 frame,
174 );
175 } else {
176 use ratatui::widgets::{Block, Borders, Paragraph, Wrap};
177 let theme = Theme::default();
178 let block = Block::default()
179 .borders(Borders::ALL)
180 .border_style(theme.panel_border)
181 .title(" Tasks ");
182 let paragraph = Paragraph::new(" Task supervisor not available.")
183 .block(block)
184 .wrap(Wrap { trim: true });
185 frame.render_widget(paragraph, layout.subagents);
186 }
187 }
188 }
189}