use ratatui::{Frame, layout::Margin};
use super::app::App;
use super::state::GenerationStatus;
use crate::tui::widgets::{
AttachmentWidget, ChatWidget, InputState, InputWidget, StatusLineWidget, StatusWidget,
};
pub fn render_ui(frame: &mut Frame, app: &mut App) {
if let Some(ref title) = app.session_state.conversation_title {
app.set_terminal_title(title);
} else {
app.set_terminal_title(&format!("mermaid - {}", app.working_dir));
}
let terminal_width = frame.area().width.saturating_sub(4) as usize; let input_lines = if app.input.is_empty() {
1
} else {
let mut lines = 1;
let mut current_line_length = 0;
for ch in app.input.get().chars() {
if ch == '\n' || current_line_length >= terminal_width {
lines += 1;
current_line_length = if ch == '\n' { 0 } else { 1 };
} else {
current_line_length += 1;
}
}
lines.min(5) };
let input_height = (input_lines + 2) as u16;
let queued_count = app.operation_state.queued_message_count();
let status_line_height = (1 + queued_count).min(6) as u16;
let attachment_height = if app.attachment_state.is_empty() {
0
} else {
1
};
let chunks = app.ui_state.layout_cache.get_main_layout(
frame.area(),
input_height,
status_line_height,
attachment_height,
);
let chat_area = chunks[0].inner(Margin {
horizontal: 1,
vertical: 0,
});
let active_subagents = app.operation_state.snapshot_subagent_progress();
let chat_widget = ChatWidget {
messages: &app.session_state.messages,
theme: &app.ui_state.theme,
markdown_cache: &mut app.ui_state.markdown_cache,
active_subagents,
};
frame.render_stateful_widget(chat_widget, chat_area, &mut app.ui_state.chat_state);
if app.app_state.is_generating() {
let elapsed_secs = app
.app_state
.generation_start_time()
.map(|start| start.elapsed().as_secs())
.unwrap_or(0);
let actual_tokens = app.app_state.tokens_received().unwrap_or(0);
let (tokens_display, tokens_estimated) =
if actual_tokens == 0 && app.response_len() > 0 {
(app.response_len() / 4, true)
} else {
(actual_tokens, false)
};
let status_line_widget = StatusLineWidget {
status: app
.app_state
.generation_status()
.unwrap_or(GenerationStatus::Idle),
elapsed_secs,
tokens_received: tokens_display,
tokens_estimated,
theme: &app.ui_state.theme,
queued_messages: app.operation_state.get_queued_messages(),
};
frame.render_widget(status_line_widget, chunks[1]);
}
if !app.attachment_state.is_empty() {
app.ui_state.attachment_area_y = Some(chunks[2].y);
let attachment_widget = AttachmentWidget {
attachments: &app.attachment_state.attachments,
theme: &app.ui_state.theme,
focused: app.ui_state.attachment_focused,
selected: app.ui_state.selected_attachment,
};
frame.render_widget(attachment_widget, chunks[2]);
} else {
app.ui_state.attachment_area_y = None;
}
let input_widget = InputWidget {
input: app.input.get(),
showing_command_hints: app.input.get().starts_with(':'),
theme: &app.ui_state.theme,
thinking_enabled: app.model_state.thinking_enabled,
};
frame.render_stateful_widget(input_widget, chunks[3], &mut app.ui_state.input_state);
if app.ui_state.attachment_focused {
} else {
let input_area = chunks[3];
let content_width = input_area.width.saturating_sub(2) as usize;
let (cursor_row, cursor_col) = InputState::calculate_cursor_position(
app.input.get(),
app.input.cursor_position,
content_width,
);
frame.set_cursor_position((input_area.x + cursor_col + 2, input_area.y + 1 + cursor_row));
}
let status_widget = StatusWidget {
theme: &app.ui_state.theme,
working_dir: &app.working_dir,
cumulative_tokens: app.session_state.cumulative_tokens,
model_name: &app.model_state.model_name,
thinking_enabled: app.model_state.thinking_enabled,
};
frame.render_widget(status_widget, chunks[4]);
}