1use agent::ContextServerRegistry;
2use collections::HashMap;
3use context_server::ContextServerId;
4use gpui::{
5 DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, ScrollHandle, Window, prelude::*,
6};
7use ui::{Divider, DividerColor, Modal, ModalHeader, WithScrollbar, prelude::*};
8use workspace::{ModalView, Workspace};
9
10pub struct ConfigureContextServerToolsModal {
11 context_server_id: ContextServerId,
12 context_server_registry: Entity<ContextServerRegistry>,
13 focus_handle: FocusHandle,
14 expanded_tools: HashMap<SharedString, bool>,
15 scroll_handle: ScrollHandle,
16}
17
18impl ConfigureContextServerToolsModal {
19 fn new(
20 context_server_id: ContextServerId,
21 context_server_registry: Entity<ContextServerRegistry>,
22 _window: &mut Window,
23 cx: &mut Context<Self>,
24 ) -> Self {
25 Self {
26 context_server_id,
27 context_server_registry,
28 focus_handle: cx.focus_handle(),
29 expanded_tools: HashMap::default(),
30 scroll_handle: ScrollHandle::new(),
31 }
32 }
33
34 pub fn toggle(
35 context_server_id: ContextServerId,
36 context_server_registry: Entity<ContextServerRegistry>,
37 workspace: &mut Workspace,
38 window: &mut Window,
39 cx: &mut Context<Workspace>,
40 ) {
41 workspace.toggle_modal(window, cx, |window, cx| {
42 Self::new(context_server_id, context_server_registry, window, cx)
43 });
44 }
45
46 fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
47 cx.emit(DismissEvent)
48 }
49
50 fn render_modal_content(
51 &self,
52 window: &mut Window,
53 cx: &mut Context<Self>,
54 ) -> impl IntoElement {
55 let tools = self
56 .context_server_registry
57 .read(cx)
58 .tools_for_server(&self.context_server_id)
59 .collect::<Vec<_>>();
60
61 div()
62 .size_full()
63 .pb_2()
64 .child(
65 v_flex()
66 .id("modal_content")
67 .px_2()
68 .gap_1()
69 .max_h_128()
70 .overflow_y_scroll()
71 .track_scroll(&self.scroll_handle)
72 .children(tools.iter().enumerate().flat_map(|(index, tool)| {
73 let tool_name = tool.name();
74 let is_expanded = self
75 .expanded_tools
76 .get(tool_name.as_ref())
77 .copied()
78 .unwrap_or(false);
79
80 let icon = if is_expanded {
81 IconName::ChevronUp
82 } else {
83 IconName::ChevronDown
84 };
85
86 let mut items = vec![
87 v_flex()
88 .child(
89 h_flex()
90 .id(SharedString::from(format!("tool-header-{}", index)))
91 .py_1()
92 .pl_1()
93 .pr_2()
94 .w_full()
95 .justify_between()
96 .rounded_sm()
97 .hover(|s| s.bg(cx.theme().colors().element_hover))
98 .child(
99 Label::new(tool_name.clone())
100 .buffer_font(cx)
101 .size(LabelSize::Small),
102 )
103 .child(
104 Icon::new(icon)
105 .size(IconSize::Small)
106 .color(Color::Muted),
107 )
108 .on_click(cx.listener({
109 move |this, _event, _window, _cx| {
110 let current = this
111 .expanded_tools
112 .get(tool_name.as_ref())
113 .copied()
114 .unwrap_or(false);
115 this.expanded_tools
116 .insert(tool_name.clone(), !current);
117 _cx.notify();
118 }
119 })),
120 )
121 .when(is_expanded, |this| {
122 this.child(
123 Label::new(tool.description()).color(Color::Muted).mx_1(),
124 )
125 })
126 .into_any_element(),
127 ];
128
129 if index < tools.len() - 1 {
130 items.push(
131 h_flex()
132 .w_full()
133 .child(Divider::horizontal().color(DividerColor::BorderVariant))
134 .into_any_element(),
135 );
136 }
137
138 items
139 })),
140 )
141 .vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
142 .into_any_element()
143 }
144}
145
146impl ModalView for ConfigureContextServerToolsModal {}
147
148impl Focusable for ConfigureContextServerToolsModal {
149 fn focus_handle(&self, _cx: &App) -> FocusHandle {
150 self.focus_handle.clone()
151 }
152}
153
154impl EventEmitter<DismissEvent> for ConfigureContextServerToolsModal {}
155
156impl Render for ConfigureContextServerToolsModal {
157 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
158 div()
159 .key_context("ContextServerToolsModal")
160 .occlude()
161 .elevation_3(cx)
162 .w(rems(34.))
163 .on_action(cx.listener(Self::cancel))
164 .track_focus(&self.focus_handle)
165 .child(
166 Modal::new("configure-context-server-tools", None::<ScrollHandle>)
167 .header(
168 ModalHeader::new()
169 .headline(format!("Tools from {}", self.context_server_id.0))
170 .show_dismiss_button(true),
171 )
172 .child(self.render_modal_content(window, cx)),
173 )
174 }
175}