ui/components/ai/
thinking_block.rs1use std::sync::Arc;
2
3use gpui::{ClickEvent, Entity};
4use markdown::Markdown;
5
6use crate::prelude::*;
7use crate::{AgentMarkdown, Disclosure};
8
9#[derive(IntoElement, RegisterComponent)]
17pub struct ThinkingBlock {
18 id: ElementId,
19 markdown: Entity<Markdown>,
20 expanded: bool,
21 on_toggle_expanded: Option<Arc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
22}
23
24impl ThinkingBlock {
25 pub fn new(id: impl Into<ElementId>, markdown: Entity<Markdown>) -> Self {
26 Self {
27 id: id.into(),
28 markdown,
29 expanded: false,
30 on_toggle_expanded: None,
31 }
32 }
33
34 pub fn expanded(mut self, expanded: bool) -> Self {
35 self.expanded = expanded;
36 self
37 }
38
39 pub fn on_toggle_expanded(
40 mut self,
41 handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
42 ) -> Self {
43 self.on_toggle_expanded = Some(Arc::new(handler));
44 self
45 }
46}
47
48impl RenderOnce for ThinkingBlock {
49 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
50 let expanded = self.expanded;
51 let disclosure_id = format!("{}-disclosure", self.id);
52 let body_id = format!("{}-md", self.id);
53
54 let header = h_flex()
55 .id(self.id.clone())
56 .gap_1()
57 .child(
58 Icon::new(IconName::ToolThink)
59 .size(IconSize::XSmall)
60 .color(Color::Muted),
61 )
62 .child(
63 Label::new("Thinking…")
64 .size(LabelSize::Small)
65 .color(Color::Muted),
66 )
67 .child(
68 Disclosure::new(disclosure_id, expanded)
69 .on_toggle_expanded(self.on_toggle_expanded),
70 );
71
72 v_flex()
73 .w_full()
74 .gap_1()
75 .child(header)
76 .when(expanded, |this| {
77 this.child(
78 div()
79 .opacity(0.7)
80 .pl_5()
81 .child(AgentMarkdown::new(body_id, self.markdown)),
82 )
83 })
84 }
85}
86
87impl Component for ThinkingBlock {
88 fn scope() -> ComponentScope {
89 ComponentScope::Agent
90 }
91
92 fn description() -> Option<&'static str> {
93 Some(
94 "A collapsible 'Thinking…' block; expansion is caller-owned so it \
95 can auto-expand while streaming and stay put once a turn finishes.",
96 )
97 }
98
99 fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
100 let entity = crate::agent_markdown_entity(
101 "Checking the test suite before making changes to `agent_message.rs`.",
102 cx,
103 );
104
105 Some(
106 v_flex()
107 .w_96()
108 .gap_4()
109 .child(single_example(
110 "Expanded",
111 ThinkingBlock::new("thinking-preview-1", entity)
112 .expanded(true)
113 .into_any_element(),
114 ))
115 .into_any_element(),
116 )
117 }
118}