mod welcome;
use super::{
render_scroll_view_persistent_with_metrics_and_theme, transcript_scroll_view_content_width,
};
use crate::tui::state::MissionControlState;
use ratatui::{
Frame,
layout::Rect,
widgets::{Paragraph, Wrap},
};
pub(super) fn draw_transcript(frame: &mut Frame<'_>, area: Rect, state: &MissionControlState) {
let theme = state.theme;
let focused = state.is_transcript_focused()
&& state.diff.dialog.is_none()
&& (state.top_modal().is_none()
|| (state.header_modal_visible() && state.modals.focused_header_modal.is_none()));
frame.render_widget(
super::bordered_block()
.title(ratatui::text::Line::styled(
"Transcript",
theme.title(focused),
))
.border_style(theme.border(focused))
.style(theme.pane()),
area,
);
let text_area = super::transcript_text_area_for_pane(area);
if state.transcript.is_empty() || crate::tui::release_notes::shows_restart_welcome(state) {
welcome::draw_welcome(frame, text_area, state);
return;
}
let visible = crate::tui::transcript_projection::visible_transcript_lines(
state,
transcript_scroll_view_content_width(text_area).max(1),
text_area.height,
);
for (row, image_row) in &visible.image_rows {
if *row >= visible.scroll_offset {
let y = row - visible.scroll_offset;
if y < usize::from(text_area.height) {
state.images.borrow_mut().place_row(
image_row,
Rect::new(
text_area.x,
text_area.y + y as u16,
transcript_scroll_view_content_width(text_area),
1,
),
);
}
}
}
state
.scroll_views
.transcript
.borrow_mut()
.set_offset(ratatui::layout::Position::new(
0,
crate::tui::transcript::usize_to_u16_saturating(visible.scroll_offset),
));
render_scroll_view_persistent_with_metrics_and_theme(
frame,
text_area,
transcript_scroll_view_content_width(text_area),
0,
visible
.scroll_offset
.saturating_add(text_area.height as usize)
.max(visible.scroll_offset.saturating_add(visible.lines.len()))
.try_into()
.unwrap_or(u16::MAX),
visible.scrollbar.content_length,
visible.scrollbar.position,
visible.scrollbar.viewport_length,
Paragraph::new(visible.lines)
.style(theme.pane())
.wrap(Wrap { trim: false }),
&state.scroll_views.transcript,
theme,
);
}
#[cfg(test)]
mod profiling {
use super::*;
use ratatui::backend::TestBackend;
#[test]
#[ignore = "release-mode TestBackend transcript measurement; run with --release --ignored --nocapture"]
fn transcript_test_backend_measurement() {
let area = Rect::new(0, 0, 120, 40);
let mut state = MissionControlState::default();
for index in 0..500 {
state.transcript.push_back(format!(
"assistant: entry {index} {}",
"wrapped transcript text ".repeat(30)
));
}
state.set_scroll_offset(&state.scroll_views.transcript, 200);
let mut terminal =
ratatui::Terminal::new(TestBackend::new(area.width, area.height)).unwrap();
terminal
.draw(|frame| draw_transcript(frame, area, &state))
.unwrap();
crate::tui::transcript_cards::reset_project_entry_count_for_profiling();
let started = std::time::Instant::now();
for _ in 0..100 {
terminal
.draw(|frame| draw_transcript(frame, area, &state))
.unwrap();
}
eprintln!(
"transcript_render backend=TestBackend physical_terminal=false frames=100 elapsed_us={} projected_cards={}",
started.elapsed().as_micros(),
crate::tui::transcript_cards::project_entry_count_for_profiling()
);
}
}