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