use super::super::{
render_scroll_view_gutter_with_theme, render_text_scroll_view_with_theme,
scroll_view_content_width, usize_to_u16_saturating,
};
use super::{SYSTEM_PROMPT_MODAL, draw_modal_panel_with_hotkeys, modal_text_area};
use crate::tui::{
state::{MissionControlState, SystemPromptCard},
theme::MissionControlTheme,
transcript::ratatui_wrapped_visual_rows,
};
use ratatui::{
Frame,
layout::Rect,
widgets::{Block, Borders, Paragraph, Wrap},
};
pub(crate) fn system_prompt_text_area(area: Rect) -> Option<Rect> {
let text_area = modal_text_area(area, "Current System Prompt", SYSTEM_PROMPT_MODAL)?;
(text_area.height > 0).then_some(text_area)
}
pub(in crate::tui::render) fn draw_system_prompt_modal(
frame: &mut Frame<'_>,
area: Rect,
state: &MissionControlState,
) {
let Some(modal) = &state.modals.system_prompt_modal else {
return;
};
let theme = state.theme;
let Some(text_area) = draw_modal_panel_with_hotkeys(
frame,
area,
"Current System Prompt",
SYSTEM_PROMPT_MODAL,
theme,
"[↑/↓] scroll • [PgUp/PgDn] page • [Home/End] • [q] close",
Some("[Esc] close"),
) else {
return;
};
if text_area.is_empty() {
return;
}
if modal.sections.is_empty() {
render_text_scroll_view_with_theme(
frame,
text_area,
&modal.content,
&state.scroll_views.system_prompt,
theme,
);
return;
}
let width = scroll_view_content_width(text_area);
let height = usize_to_u16_saturating(modal.visual_rows(width)).max(text_area.height);
let offset = state
.scroll_views
.system_prompt
.borrow()
.offset()
.y
.min(height.saturating_sub(text_area.height));
let viewport = Rect { width, ..text_area };
frame.render_widget(Block::default().style(theme.app()), viewport);
let summary_rows = ratatui_wrapped_visual_rows(&modal.content, width.max(1));
if usize::from(offset) < summary_rows {
let summary_area = Rect {
height: usize_to_u16_saturating(summary_rows - usize::from(offset))
.min(viewport.height),
..viewport
};
frame.render_widget(
Paragraph::new(modal.content.as_str())
.style(theme.muted(false))
.wrap(Wrap { trim: false })
.scroll((offset, 0)),
summary_area,
);
}
let mut start = summary_rows.saturating_add(1);
for card in &modal.sections {
let rows = card.visual_rows(width);
if start >= usize::from(offset) + usize::from(viewport.height) {
break;
}
if start.saturating_add(rows) > usize::from(offset) {
draw_card(
frame,
viewport,
card,
start,
rows,
usize::from(offset),
theme,
);
}
start = start.saturating_add(rows).saturating_add(1);
}
render_scroll_view_gutter_with_theme(frame, text_area, width, height, offset, None, theme);
}
fn draw_card(
frame: &mut Frame<'_>,
viewport: Rect,
card: &SystemPromptCard,
start: usize,
rows: usize,
offset: usize,
theme: MissionControlTheme,
) {
let end = start.saturating_add(rows);
let visible_start = start.max(offset);
let visible_end = end.min(offset + usize::from(viewport.height));
let mut borders = Borders::LEFT | Borders::RIGHT;
if visible_start == start {
borders |= Borders::TOP;
}
if visible_end == end {
borders |= Borders::BOTTOM;
}
let area = Rect::new(
viewport.x,
viewport.y + usize_to_u16_saturating(visible_start - offset),
viewport.width,
usize_to_u16_saturating(visible_end - visible_start),
);
frame.render_widget(
Block::default()
.borders(borders)
.border_style(theme.border(false))
.style(theme.app()),
area,
);
let text_start = start.saturating_add(1);
let visible_text_start = text_start.max(visible_start);
let visible_text_end = end.saturating_sub(1).min(visible_end);
if viewport.width <= 4 || visible_text_start >= visible_text_end {
return;
}
let mut text = card.text();
text.lines[0].style = theme.title(true);
text.lines[1].style = theme.accent(true);
frame.render_widget(
Paragraph::new(text)
.style(theme.pane())
.wrap(Wrap { trim: false })
.scroll((usize_to_u16_saturating(visible_text_start - text_start), 0)),
Rect::new(
viewport.x + 2,
viewport.y + usize_to_u16_saturating(visible_text_start - offset),
SystemPromptCard::text_width(viewport.width),
usize_to_u16_saturating(visible_text_end - visible_text_start),
),
);
}