hematite/ui/
modal_review.rs1use ratatui::{
2 layout::{Constraint, Direction, Layout, Rect},
3 style::{Color, Modifier, Style, Stylize},
4 widgets::{Block, Borders, Clear, Paragraph, Wrap},
5 Frame,
6};
7
8pub struct ActiveReview {
9 pub worker_id: String,
10 pub file_path: String,
11 pub before: String,
12 pub after: String,
13 pub tx: tokio::sync::oneshot::Sender<crate::agent::swarm::ReviewResponse>,
14}
15
16pub fn draw_diff_review(f: &mut Frame, review: &ActiveReview) {
17 let area = centered_rect(80, 80, f.size());
18 f.render_widget(Clear, area);
19
20 let rust_color = Color::Rgb(120, 70, 50);
22 let outer_block = Block::default()
23 .borders(Borders::ALL)
24 .border_style(Style::default().fg(rust_color))
25 .style(Style::default().bg(Color::Rgb(15, 10, 5))); let inner_area = outer_block.inner(area);
28 f.render_widget(outer_block, area);
29
30 let chunks = Layout::default()
32 .direction(Direction::Vertical)
33 .margin(1) .constraints(
35 [
36 Constraint::Length(3), Constraint::Percentage(40), Constraint::Percentage(40), Constraint::Length(3), ]
41 .as_ref(),
42 )
43 .split(inner_area);
44
45 let title = if review.worker_id.is_empty() {
46 "⚠ SYNTHESIS MERGE CONFLICT ⚠".to_string()
47 } else {
48 format!("⚙ SWARM SYNTHESIS: Worker {} ⚙", review.worker_id)
49 };
50
51 let header = Paragraph::new(title)
52 .block(
53 Block::default()
54 .borders(Borders::ALL)
55 .style(Style::default().fg(Color::Yellow)),
56 )
57 .alignment(ratatui::layout::Alignment::Center);
58 f.render_widget(header, chunks[0]);
59
60 let before_para = Paragraph::new(review.before.clone())
62 .block(
63 Block::default()
64 .title(" Original Context ")
65 .borders(Borders::ALL)
66 .fg(Color::Red),
67 )
68 .wrap(Wrap { trim: true });
69 f.render_widget(before_para, chunks[1]);
70
71 let cleaned_after = review
74 .after
75 .replace("<thought>", "")
76 .replace("</thought>", "")
77 .replace("<think>", "")
78 .replace("</think>", "");
79 let final_after = if let Some(start) = cleaned_after.find("```") {
80 let rest = &cleaned_after[start + 3..];
81 if let Some(end) = rest.find("```") {
82 if let Some(first_line) = rest.find('\n') {
84 if first_line < end {
85 rest[first_line + 1..end].trim().to_string()
86 } else {
87 rest[..end].trim().to_string()
88 }
89 } else {
90 rest[..end].trim().to_string()
91 }
92 } else {
93 cleaned_after
94 }
95 } else {
96 cleaned_after
97 };
98
99 let after_para = Paragraph::new(final_after)
100 .block(
101 Block::default()
102 .title(" AI Synthesized Patch ")
103 .borders(Borders::ALL)
104 .fg(Color::Cyan),
105 )
106 .wrap(Wrap { trim: true });
107 f.render_widget(after_para, chunks[2]);
108
109 let footer = Paragraph::new(" [Y] Accept | [N] Abort | [R] Retry Synthesis ")
111 .block(
112 Block::default()
113 .borders(Borders::ALL)
114 .style(Style::default().add_modifier(Modifier::BOLD)),
115 )
116 .alignment(ratatui::layout::Alignment::Center);
117 f.render_widget(footer, chunks[3]);
118}
119
120fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
121 let popup_layout = Layout::default()
122 .direction(Direction::Vertical)
123 .constraints(
124 [
125 Constraint::Percentage((100 - percent_y) / 2),
126 Constraint::Percentage(percent_y),
127 Constraint::Percentage((100 - percent_y) / 2),
128 ]
129 .as_ref(),
130 )
131 .split(r);
132
133 Layout::default()
134 .direction(Direction::Horizontal)
135 .constraints(
136 [
137 Constraint::Percentage((100 - percent_x) / 2),
138 Constraint::Percentage(percent_x),
139 Constraint::Percentage((100 - percent_x) / 2),
140 ]
141 .as_ref(),
142 )
143 .split(popup_layout[1])[1]
144}