ui/components/ai/
agent_thread_view.rs1use gpui::{FollowMode, ListAlignment, ListState, SharedString, list};
2
3use crate::prelude::*;
4
5#[derive(IntoElement, RegisterComponent)]
16pub struct AgentThreadView {
17 id: ElementId,
18 list_state: ListState,
19 render_item: Box<dyn FnMut(usize, &mut Window, &mut App) -> AnyElement + 'static>,
20 sticky_to_bottom: bool,
21}
22
23impl AgentThreadView {
24 pub fn new(
25 id: impl Into<ElementId>,
26 list_state: ListState,
27 render_item: impl FnMut(usize, &mut Window, &mut App) -> AnyElement + 'static,
28 ) -> Self {
29 Self {
30 id: id.into(),
31 list_state,
32 render_item: Box::new(render_item),
33 sticky_to_bottom: false,
34 }
35 }
36
37 pub fn sticky_to_bottom(mut self, sticky: bool) -> Self {
41 self.sticky_to_bottom = sticky;
42 self
43 }
44}
45
46impl RenderOnce for AgentThreadView {
47 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
48 self.list_state.set_follow_mode(if self.sticky_to_bottom {
49 FollowMode::Tail
50 } else {
51 FollowMode::Normal
52 });
53
54 div()
55 .id(self.id)
56 .size_full()
57 .child(list(self.list_state, self.render_item).size_full())
58 }
59}
60
61impl Component for AgentThreadView {
62 fn scope() -> ComponentScope {
63 ComponentScope::Agent
64 }
65
66 fn name() -> &'static str {
67 "AgentThreadView"
68 }
69
70 fn description() -> Option<&'static str> {
71 Some(
72 "A scrollable agent chat transcript backed by gpui's virtualized \
73 list element. The caller owns the ListState and item data; this \
74 component only wires up rendering and sticky-to-bottom follow mode.",
75 )
76 }
77
78 fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
79 let messages: Vec<SharedString> = vec![
80 "User: How do I run tests?".into(),
81 "Assistant: Run `cargo test -p boltz-ui`.".into(),
82 "User: Thanks!".into(),
83 ];
84
85 let list_state = ListState::new(messages.len(), ListAlignment::Bottom, px(256.));
86 let border_color = cx.theme().colors().border_variant;
87
88 Some(
89 v_flex()
90 .gap_4()
91 .child(single_example(
92 "Default",
93 div()
94 .w_96()
95 .h(px(160.))
96 .border_1()
97 .border_color(border_color)
98 .child(
99 AgentThreadView::new("thread-preview", list_state, move |ix, _, _| {
100 div()
101 .px_2()
102 .py_1()
103 .child(Label::new(messages[ix].clone()))
104 .into_any_element()
105 })
106 .sticky_to_bottom(true),
107 )
108 .into_any_element(),
109 ))
110 .into_any_element(),
111 )
112 }
113}