use ratatui::{
layout::{Alignment, Constraint, Layout, Rect},
style::{Color, Style},
widgets::{Block, Borders, Clear, Paragraph},
Frame,
};
use super::app::App;
use super::conversation::render_conversation;
use super::widgets::spinner::render_spinner;
use super::widgets::status_bar::render_header;
use super::widgets::thread_view::render_thread;
pub fn render(frame: &mut Frame, app: &mut App, recent_count: usize) {
let [header, body, footer] = Layout::vertical([
Constraint::Length(2), Constraint::Fill(1), Constraint::Length(1), ])
.areas(frame.area());
render_header(frame, header, app);
if app.is_streaming {
let spinner_area = Rect {
x: header.x + header.width.saturating_sub(20),
y: header.y,
width: 20.min(header.width),
height: 1,
};
render_spinner(frame, spinner_area, &mut app.spinner_state, "");
}
if app.show_conversation {
let [conv_area, main_area] =
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
.areas(body);
render_conversation(
frame,
conv_area,
app.conversation.items(),
app.conversation_scroll,
&app.thinking_collapsed,
);
render_body(frame, main_area, app, recent_count);
} else {
render_body(frame, body, app, recent_count);
}
render_footer(frame, footer, app);
if app.is_paused {
render_pause_overlay(frame, body);
}
}
fn render_body(frame: &mut Frame, area: Rect, app: &App, recent_count: usize) {
let block = Block::default()
.borders(Borders::TOP)
.border_style(Style::default().fg(Color::DarkGray));
let inner = block.inner(area);
frame.render_widget(block, area);
render_thread(frame, inner, app, recent_count);
}
fn render_footer(frame: &mut Frame, area: Rect, app: &App) {
let key_hints = "j/k:scroll Tab:select Enter:toggle {/}:iteration c:conversation t:thinking p:pause Ctrl+C:quit";
let log_display = app
.log_path
.as_ref()
.map(|p| format!("Log: {}", p.display()))
.unwrap_or_default();
if log_display.is_empty() {
let paragraph = Paragraph::new(key_hints).style(Style::default().fg(Color::DarkGray));
frame.render_widget(paragraph, area);
} else {
let [left, right] =
Layout::horizontal([Constraint::Fill(1), Constraint::Fill(1)]).areas(area);
frame.render_widget(
Paragraph::new(key_hints).style(Style::default().fg(Color::DarkGray)),
left,
);
frame.render_widget(
Paragraph::new(log_display)
.style(Style::default().fg(Color::DarkGray))
.alignment(Alignment::Right),
right,
);
}
}
fn render_pause_overlay(frame: &mut Frame, area: Rect) {
let message = "PAUSED - press p to resume";
let width = message.len() as u16 + 4;
let height = 3;
let popup_area = Rect {
x: area.x + (area.width.saturating_sub(width)) / 2,
y: area.y + (area.height.saturating_sub(height)) / 2,
width: width.min(area.width),
height: height.min(area.height),
};
frame.render_widget(Clear, popup_area);
let block = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::Yellow));
let text = Paragraph::new(message)
.alignment(Alignment::Center)
.block(block);
frame.render_widget(text, popup_area);
}