diff --git a/crates/assistant2/src/assistant.rs b/crates/assistant2/src/assistant.rs index 5224b097cc..6e97bd9d8e 100644 --- a/crates/assistant2/src/assistant.rs +++ b/crates/assistant2/src/assistant.rs @@ -32,6 +32,7 @@ use prompt_store::PromptBuilder; use settings::Settings as _; pub use crate::active_thread::ActiveThread; +use crate::assistant_configuration::AddContextServerModal; pub use crate::assistant_panel::{AssistantPanel, ConcreteAssistantPanelDelegate}; pub use crate::inline_assistant::InlineAssistant; pub use crate::thread::{Message, RequestKind, Thread, ThreadEvent}; @@ -46,6 +47,7 @@ actions!( RemoveAllContext, OpenHistory, OpenConfiguration, + AddContextServer, RemoveSelectedThread, Chat, ChatMode, @@ -86,6 +88,7 @@ pub fn init( client.telemetry().clone(), cx, ); + cx.observe_new(AddContextServerModal::register).detach(); feature_gate_assistant2_actions(cx); } diff --git a/crates/assistant2/src/assistant_configuration.rs b/crates/assistant2/src/assistant_configuration.rs index 54e5266c93..b215709be2 100644 --- a/crates/assistant2/src/assistant_configuration.rs +++ b/crates/assistant2/src/assistant_configuration.rs @@ -1,3 +1,5 @@ +mod add_context_server_modal; + use std::sync::Arc; use assistant_tool::{ToolSource, ToolWorkingSet}; @@ -5,12 +7,14 @@ use collections::HashMap; use context_server::manager::ContextServerManager; use gpui::{Action, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable, Subscription}; use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry}; -use ui::{ - prelude::*, Disclosure, Divider, DividerColor, ElevationIndex, Indicator, Switch, Tooltip, -}; +use ui::{prelude::*, Disclosure, Divider, DividerColor, ElevationIndex, Indicator, Switch}; use util::ResultExt as _; use zed_actions::ExtensionCategoryFilter; +pub(crate) use add_context_server_modal::AddContextServerModal; + +use crate::AddContextServer; + pub struct AssistantConfiguration { focus_handle: FocusHandle, configuration_views_by_provider: HashMap, @@ -307,8 +311,9 @@ impl AssistantConfiguration { .icon(IconName::Plus) .icon_size(IconSize::Small) .icon_position(IconPosition::Start) - .disabled(true) - .tooltip(Tooltip::text("Not yet implemented")), + .on_click(|_event, window, cx| { + window.dispatch_action(AddContextServer.boxed_clone(), cx) + }), ), ) .child( diff --git a/crates/assistant2/src/assistant_configuration/add_context_server_modal.rs b/crates/assistant2/src/assistant_configuration/add_context_server_modal.rs new file mode 100644 index 0000000000..68ed2585cc --- /dev/null +++ b/crates/assistant2/src/assistant_configuration/add_context_server_modal.rs @@ -0,0 +1,148 @@ +use context_server::{ContextServerSettings, ServerCommand, ServerConfig}; +use editor::Editor; +use gpui::{prelude::*, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, WeakEntity}; +use serde_json::json; +use settings::update_settings_file; +use ui::{prelude::*, Modal, ModalFooter, ModalHeader, Section}; +use workspace::{ModalView, Workspace}; + +use crate::AddContextServer; + +pub struct AddContextServerModal { + workspace: WeakEntity, + name_editor: Entity, + command_editor: Entity, +} + +impl AddContextServerModal { + pub fn register( + workspace: &mut Workspace, + _window: Option<&mut Window>, + _cx: &mut Context, + ) { + workspace.register_action(|workspace, _: &AddContextServer, window, cx| { + let workspace_handle = cx.entity().downgrade(); + workspace.toggle_modal(window, cx, |window, cx| { + Self::new(workspace_handle, window, cx) + }) + }); + } + + pub fn new( + workspace: WeakEntity, + window: &mut Window, + cx: &mut Context, + ) -> Self { + let name_editor = cx.new(|cx| Editor::single_line(window, cx)); + let command_editor = cx.new(|cx| Editor::single_line(window, cx)); + + name_editor.update(cx, |editor, cx| { + editor.set_placeholder_text("Context server name", cx); + }); + + command_editor.update(cx, |editor, cx| { + editor.set_placeholder_text("Command to run the context server", cx); + }); + + Self { + name_editor, + command_editor, + workspace, + } + } + + fn confirm(&mut self, cx: &mut Context) { + let name = self.name_editor.read(cx).text(cx).trim().to_string(); + let command = self.command_editor.read(cx).text(cx).trim().to_string(); + + if name.is_empty() || command.is_empty() { + return; + } + + let mut command_parts = command.split(' ').map(|part| part.trim().to_string()); + let Some(path) = command_parts.next() else { + return; + }; + let args = command_parts.collect::>(); + + if let Some(workspace) = self.workspace.upgrade() { + workspace.update(cx, |workspace, cx| { + let fs = workspace.app_state().fs.clone(); + update_settings_file::(fs.clone(), cx, |settings, _| { + settings.context_servers.insert( + name.into(), + ServerConfig { + command: Some(ServerCommand { + path, + args, + env: None, + }), + settings: Some(json!({})), + }, + ); + }); + }); + } + + cx.emit(DismissEvent); + } + + fn cancel(&mut self, cx: &mut Context) { + cx.emit(DismissEvent); + } +} + +impl ModalView for AddContextServerModal {} + +impl Focusable for AddContextServerModal { + fn focus_handle(&self, cx: &App) -> FocusHandle { + self.name_editor.focus_handle(cx).clone() + } +} + +impl EventEmitter for AddContextServerModal {} + +impl Render for AddContextServerModal { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + div() + .elevation_3(cx) + .w(rems(34.)) + .key_context("AddContextServerModal") + .on_action(cx.listener(|this, _: &menu::Cancel, _window, cx| this.cancel(cx))) + .on_action(cx.listener(|this, _: &menu::Confirm, _window, cx| this.confirm(cx))) + .capture_any_mouse_down(cx.listener(|this, _, window, cx| { + this.focus_handle(cx).focus(window); + })) + .on_mouse_down_out(cx.listener(|_this, _, _, cx| cx.emit(DismissEvent))) + .child( + Modal::new("add-context-server", None) + .header(ModalHeader::new().headline("Add Context Server")) + .section( + Section::new() + .child( + v_flex() + .gap_1() + .child(Label::new("Name")) + .child(self.name_editor.clone()), + ) + .child( + v_flex() + .gap_1() + .child(Label::new("Command")) + .child(self.command_editor.clone()), + ), + ) + .footer( + ModalFooter::new() + .start_slot( + Button::new("cancel", "Cancel").on_click( + cx.listener(|this, _event, _window, cx| this.cancel(cx)), + ), + ) + .end_slot(Button::new("add-server", "Add Server").on_click( + cx.listener(|this, _event, _window, cx| this.confirm(cx)), + )), + ), + ) + } +}