@@ -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);
}
@@ -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<LanguageModelProviderId, AnyView>,
@@ -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(
@@ -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<Workspace>,
+ name_editor: Entity<Editor>,
+ command_editor: Entity<Editor>,
+}
+
+impl AddContextServerModal {
+ pub fn register(
+ workspace: &mut Workspace,
+ _window: Option<&mut Window>,
+ _cx: &mut Context<Workspace>,
+ ) {
+ 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<Workspace>,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> 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<Self>) {
+ 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::<Vec<_>>();
+
+ if let Some(workspace) = self.workspace.upgrade() {
+ workspace.update(cx, |workspace, cx| {
+ let fs = workspace.app_state().fs.clone();
+ update_settings_file::<ContextServerSettings>(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<Self>) {
+ 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<DismissEvent> for AddContextServerModal {}
+
+impl Render for AddContextServerModal {
+ fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> 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)),
+ )),
+ ),
+ )
+ }
+}