rab/agent/ui/components/
user_message.rs1use std::cell::RefCell;
2
3use crate::agent::ui::theme::current_theme;
4use crate::tui::Component;
5use crate::tui::components::r#box::TuiBox;
6use crate::tui::components::markdown::{DefaultTextStyle, Markdown, MarkdownOptions};
7
8const OSC133_ZONE_START: &str = "\x1b]133;A\x07";
9const OSC133_ZONE_END: &str = "\x1b]133;B\x07";
10const OSC133_ZONE_FINAL: &str = "\x1b]133;C\x07";
11
12pub struct UserMessageComponent {
15 box_component: TuiBox,
16 cached_lines: RefCell<Option<Vec<String>>>,
17 cached_width: RefCell<usize>,
18}
19
20impl UserMessageComponent {
21 pub fn new(text: impl Into<String>) -> Self {
22 let text = text.into();
23 let theme = current_theme();
24 let bg_ansi = theme.bg_ansi("userMessageBg").to_string();
25 drop(theme);
26
27 let mut msg_box = TuiBox::new(
28 1,
29 1,
30 Some(std::boxed::Box::new(move |s: &str| -> String {
31 format!("{}{}\x1b[49m", bg_ansi, s)
32 })),
33 );
34
35 let md_theme = crate::agent::ui::theme::get_markdown_theme();
37 let default_style = DefaultTextStyle {
38 color: Some(std::sync::Arc::new(|s: &str| -> String {
39 let t = current_theme();
40 t.fg("userMessageText", s)
41 })),
42 bg_color: None,
43 bold: false,
44 italic: false,
45 strikethrough: false,
46 underline: false,
47 };
48 let md = Markdown::new(
49 text.clone(),
50 0,
51 0,
52 md_theme,
53 Some(default_style),
54 Some(MarkdownOptions {
55 preserve_ordered_list_markers: true,
56 }),
57 );
58 msg_box.add_child(std::boxed::Box::new(md));
59
60 Self {
61 box_component: msg_box,
62 cached_lines: RefCell::new(None),
63 cached_width: RefCell::new(0),
64 }
65 }
66}
67
68impl Component for UserMessageComponent {
69 fn set_expanded(&mut self, _expanded: bool) {
70 }
72
73 fn render(&self, width: usize) -> Vec<String> {
74 if *self.cached_width.borrow() == width
75 && let Some(ref lines) = *self.cached_lines.borrow()
76 {
77 return lines.clone();
78 }
79
80 let mut lines = self.box_component.render(width);
81 if !lines.is_empty() {
82 lines[0] = format!("{}{}", OSC133_ZONE_START, &lines[0]);
83 if let Some(last) = lines.last_mut() {
84 last.push_str(OSC133_ZONE_END);
85 last.push_str(OSC133_ZONE_FINAL);
86 }
87 }
88
89 let result = lines.clone();
91 *self.cached_lines.borrow_mut() = Some(lines);
92 *self.cached_width.borrow_mut() = width;
93 result
94 }
95
96 fn invalidate(&mut self) {
97 *self.cached_lines.borrow_mut() = None;
98 self.box_component.invalidate();
99 }
100}