1use crate::console::strip_ansi_codes;
4use crate::exceptions::AicoError;
5use crate::historystore::reconstruct::reconstruct_history;
6use crate::models::Role;
7use crate::session::Session;
8use comfy_table::presets::NOTHING;
9use comfy_table::*;
10
11fn create_log_row(id: &str, role: &str, snippet: &str, color: Color, is_excluded: bool) -> Row {
13 let cells = vec![
14 Cell::new(id).set_alignment(CellAlignment::Right),
15 Cell::new(role).fg(color),
16 Cell::new(snippet),
17 ];
18
19 if is_excluded {
20 Row::from(
21 cells
22 .into_iter()
23 .map(|cell| cell.add_attribute(Attribute::Dim)),
24 )
25 } else {
26 Row::from(cells)
27 }
28}
29
30pub fn run() -> Result<(), AicoError> {
31 let session = Session::load_active()?;
32 let width = crate::console::get_terminal_width();
33
34 let history_vec = reconstruct_history(&session.store, &session.view, true)?;
36
37 let mut paired_history = Vec::new();
38 let mut dangling_history = Vec::new();
39
40 let mut iter = history_vec.iter().peekable();
42
43 while let Some(current) = iter.next() {
44 let is_pair = current.record.role == Role::User
46 && iter.peek().is_some_and(|next| {
47 next.record.role == Role::Assistant && next.pair_index == current.pair_index
48 });
49
50 if is_pair {
51 paired_history.push(current);
52 paired_history.push(iter.next().unwrap());
54 } else {
55 dangling_history.push(current);
56 }
57 }
58
59 if paired_history.is_empty() && dangling_history.is_empty() {
60 println!("No message pairs found in active history.");
61 return Ok(());
62 }
63
64 let mut table = Table::new();
65 table
66 .load_preset(NOTHING)
67 .set_width(width as u16)
68 .set_content_arrangement(ContentArrangement::Dynamic);
69
70 table.set_header(vec![
72 Cell::new("ID")
73 .add_attribute(Attribute::Bold)
74 .set_alignment(CellAlignment::Right),
75 Cell::new("Role").add_attribute(Attribute::Bold),
76 Cell::new("Message Snippet").add_attribute(Attribute::Bold),
77 ]);
78
79 table
81 .column_mut(0)
82 .unwrap()
83 .set_constraint(ColumnConstraint::ContentWidth);
84 table
85 .column_mut(1)
86 .unwrap()
87 .set_constraint(ColumnConstraint::ContentWidth);
88 let snippet_col = table.column_mut(2).unwrap();
89 snippet_col.set_constraint(ColumnConstraint::LowerBoundary(Width::Fixed(20)));
90
91 for item in paired_history {
92 let pair_idx = item.pair_index;
93 let is_excluded = item.is_excluded;
94
95 let id_display = if is_excluded {
96 format!("{}[-]", pair_idx)
97 } else {
98 pair_idx.to_string()
99 };
100
101 let (role_name, role_color) = match item.record.role {
102 Role::User => ("user", Color::Blue),
103 Role::Assistant => ("assistant", Color::Green),
104 Role::System => ("system", Color::Grey),
105 };
106
107 let snippet = item.record.content.lines().next().unwrap_or("").trim();
108
109 table.add_row(create_log_row(
110 if item.record.role == Role::User {
111 &id_display
112 } else {
113 ""
114 },
115 role_name,
116 snippet,
117 role_color,
118 is_excluded,
119 ));
120 }
121
122 let table_output = table.to_string();
123
124 let plain_output = strip_ansi_codes(&table_output);
126 let table_full_width = plain_output.lines().next().unwrap_or("").len();
127
128 let title = "Active Context Log";
129 if table_full_width > title.len() {
130 let padding = (table_full_width - title.len()) / 2;
131 println!("{}{}{}", " ".repeat(padding), title, " ".repeat(padding));
132 } else {
133 println!("{}", title);
134 }
135
136 println!("{}", table_output);
137
138 if !dangling_history.is_empty() {
139 println!("\nDangling messages in active context:");
140 for item in dangling_history {
141 let role_name = match item.record.role {
142 Role::User => "user",
143 Role::Assistant => "assistant",
144 Role::System => "system",
145 };
146 let snippet = item.record.content.lines().next().unwrap_or("").trim();
147 println!("{}: {}", role_name, snippet);
148 }
149 }
150
151 Ok(())
152}