返回 CodeWhale
archived_context.rs
根目录 / crates / tui / src / tui / history / archived_context.rs
1 //! Parsing and rendering for archived-context transcript cells.
2
3 use ratatui::style::{Modifier, Style};
4 use ratatui::text::{Line, Span};
5
6 use crate::palette;
7
8 use super::{HistoryCell, TRANSCRIPT_RAIL};
9
10 /// Parse an `<archived_context>` block from an assistant Text block.
11 ///
12 /// Returns `Some(HistoryCell::ArchivedContext)` when the text contains a
13 /// well-formed `<archived_context>...</archived_context>` block, or `None`
14 /// if the text is regular assistant content.
15 pub(super) fn parse_archived_context(text: &str) -> Option<HistoryCell> {
16 let text = text.trim();
17 if !text.starts_with("<archived_context") || !text.ends_with("</archived_context>") {
18 return None;
19 }
20
21 let tag_end = text.find('>')?;
22 let tag = &text[..tag_end];
23
24 let level = archived_context_attr(tag, "level")
25 .and_then(|v| v.parse::<u8>().ok())
26 .unwrap_or(0);
27
28 let range = archived_context_attr(tag, "range").unwrap_or_default();
29
30 let tokens = archived_context_attr(tag, "tokens").unwrap_or_default();
31
32 let density = archived_context_attr(tag, "density").unwrap_or_default();
33
34 let model = archived_context_attr(tag, "model").unwrap_or_default();
35
36 let timestamp = archived_context_attr(tag, "timestamp").unwrap_or_default();
37
38 let close_tag = text.rfind("</archived_context>")?;
39 let summary_start = tag_end + 1;
40 let summary = text[summary_start..close_tag].trim().to_string();
41
42 Some(HistoryCell::ArchivedContext {
43 level,
44 range,
45 tokens,
46 density,
47 model,
48 timestamp,
49 summary,
50 })
51 }
52
53 fn archived_context_attr(tag: &str, name: &str) -> Option<String> {
54 let needle = format!("{name}=\"");
55 let start = tag.find(&needle)? + needle.len();
56 let rest = &tag[start..];
57 let end = rest.find('"')?;
58 Some(rest[..end].to_string())
59 }
60
61 /// Render an `<archived_context>` block with dimmed/italic styling.
62 pub(super) fn render_archived_context(
63 cell: &HistoryCell,
64 width: u16,
65 _low_motion: bool,
66 ) -> Vec<Line<'static>> {
67 let HistoryCell::ArchivedContext {
68 level,
69 range,
70 tokens,
71 density,
72 model,
73 timestamp,
74 summary,
75 } = cell
76 else {
77 return Vec::new();
78 };
79
80 let body = if summary.is_empty() {
81 "(no summary)".to_string()
82 } else {
83 summary.clone()
84 };
85
86 let label = format!("Context L{level}");
87 let label_style = Style::default()
88 .fg(palette::TEXT_DIM)
89 .add_modifier(Modifier::BOLD);
90 let body_style = Style::default().fg(palette::TEXT_DIM).italic();
91
92 let content_width = width.saturating_sub(4).max(1);
93
94 let mut lines = Vec::new();
95
96 let range_display = if range.is_empty() {
97 String::new()
98 } else {
99 range.to_string()
100 };
101 let mut header = format!("{label} {range_display}");
102 if !tokens.is_empty() {
103 header.push_str(&format!(" {tokens}"));
104 }
105 if !density.is_empty() && density != tokens {
106 header.push_str(&format!(" {density}"));
107 }
108 lines.push(Line::from(Span::styled(header, label_style)));
109
110 let model_display = if model.is_empty() {
111 String::new()
112 } else {
113 format!("via {model}")
114 };
115 let ts_display = if timestamp.is_empty() {
116 String::new()
117 } else {
118 timestamp.clone()
119 };
120 let mut sub = String::new();
121 if !model_display.is_empty() {
122 sub.push_str(&model_display);
123 }
124 if !ts_display.is_empty() {
125 if !sub.is_empty() {
126 sub.push_str(" · ");
127 }
128 sub.push_str(&ts_display);
129 }
130 if !sub.is_empty() {
131 lines.push(Line::from(Span::styled(
132 sub,
133 Style::default().fg(palette::TEXT_MUTED),
134 )));
135 }
136
137 let rendered = crate::tui::markdown_render::render_markdown(&body, content_width, body_style);
138 for (idx, line) in rendered.into_iter().enumerate() {
139 if idx == 0 {
140 let mut spans = vec![Span::styled(
141 TRANSCRIPT_RAIL.to_string(),
142 Style::default().fg(palette::TEXT_DIM),
143 )];
144 spans.extend(line.spans);
145 lines.push(Line::from(spans));
146 } else {
147 let mut spans = vec![Span::raw(" ")];
148 spans.extend(line.spans);
149 lines.push(Line::from(spans));
150 }
151 }
152
153 lines.push(Line::from(""));
154
155 lines
156 }
157
157 lines RUST