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}