use super::{
ThinkBlockMeta, TranscriptEntry, TranscriptRole, assistant_segments_to_lines_with_meta,
default_empty_transcript_line, parse_assistant_segments, transcript_prefix_style,
};
use crate::app::agent::agent::loop_::cli::theme::TEXT_MUTED;
use ratatui::style::Style;
use ratatui::text::{Line, Span};
use std::collections::BTreeSet;
fn assistant_think_id_salt(visible_entry_idx: usize) -> u64 {
u64::try_from(visible_entry_idx).unwrap_or(u64::MAX)
}
pub(crate) fn transcript_to_lines<'a>(
transcript: &'a [TranscriptEntry],
expand_tool_blocks: bool,
expand_think_all: bool,
think_detail_overrides: &BTreeSet<u64>,
draft: &'a str,
) -> (Vec<Line<'a>>, Vec<Option<ThinkBlockMeta>>) {
let mut lines: Vec<Line<'a>> = Vec::new();
let mut think_map: Vec<Option<ThinkBlockMeta>> = Vec::new();
let mut last_visible_entry_at: Option<chrono::DateTime<chrono::Local>> = None;
let mut has_visible_entry = false;
let mut visible_entry_idx = 0usize;
if transcript.is_empty() {
lines.push(default_empty_transcript_line());
think_map.push(None);
} else {
for entry in transcript {
if matches!(entry.role, TranscriptRole::Progress | TranscriptRole::Error) {
continue;
}
if has_visible_entry {
lines.push(Line::from(Span::raw("")));
think_map.push(None);
}
let (prefix, _color, prefix_style) = transcript_prefix_style(entry.role);
if matches!(entry.role, TranscriptRole::Assistant) {
let think_duration_secs = last_visible_entry_at.map(|at| {
entry.at.signed_duration_since(at).num_seconds().max(0).cast_unsigned()
});
let (mut rendered, mut rendered_meta) = assistant_segments_to_lines_with_meta(
parse_assistant_segments(&entry.text),
expand_tool_blocks,
expand_think_all,
think_detail_overrides,
think_duration_secs,
assistant_think_id_salt(visible_entry_idx),
);
if rendered.is_empty() {
rendered.push(Line::from(Span::raw("")));
rendered_meta.push(None);
}
for (idx, line) in rendered.into_iter().enumerate() {
let mut spans = line.spans;
if idx == 0 && !prefix.is_empty() {
let mut prefixed = Vec::with_capacity(spans.len().saturating_add(1));
prefixed.push(Span::styled(prefix.to_string(), prefix_style));
prefixed.append(&mut spans);
lines.push(Line::from(prefixed));
} else {
lines.push(Line::from(spans));
}
let meta = rendered_meta.get(idx).and_then(|v| *v);
think_map.push(meta);
}
} else {
for (idx, raw_line) in entry.text.lines().enumerate() {
if idx == 0 {
if prefix.is_empty() {
lines.push(Line::from(Span::raw(raw_line)));
} else {
lines.push(Line::from(vec![
Span::styled(prefix.to_string(), prefix_style),
Span::raw(raw_line),
]));
}
} else {
lines.push(Line::from(Span::raw(raw_line)));
}
think_map.push(None);
}
}
last_visible_entry_at = Some(entry.at);
has_visible_entry = true;
visible_entry_idx = visible_entry_idx.saturating_add(1);
}
}
if !draft.trim().is_empty() {
lines.push(Line::from(vec![Span::styled(draft, Style::default().fg(TEXT_MUTED))]));
think_map.push(None);
}
(lines, think_map)
}