mod add_llm_provider_modal;pub mod configure_context_server_modal;mod configure_context_server_tools_modal;mod manage_profiles_modal;mod tool_picker; use std::{ops::Range, sync::Arc}; use agent::ContextServerRegistry;use anyhow::Result;use cloud_api_types::Plan;use collections::HashMap;use context_server::ContextServerId;use editor::{Editor, MultiBufferOffset, SelectionEffects, scroll::Autoscroll};use extension::ExtensionManifest;use extension_host::ExtensionStore;use fs::Fs;use gpui::{ Action, AnyView, App, AsyncWindowContext, Corner, Entity, EventEmitter, FocusHandle, Focusable, ScrollHandle, Subscription, Task, WeakEntity,};use itertools::Itertools;use language::LanguageRegistry;use language_model::{ IconOrSvg, LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID,};use language_models::AllLanguageModelSettings;use notifications::status_toast::{StatusToast, ToastIcon};use project::{ agent_server_store::{AgentId, AgentServerStore, ExternalAgentSource}, context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore},};use settings::{Settings, SettingsStore, update_settings_file};use ui::{ ButtonStyle, Chip, CommonAnimationExt, ContextMenu, ContextMenuEntry, Disclosure, Divider, DividerColor, ElevationIndex, Indicator, LabelSize, PopoverMenu, Switch, Tooltip, WithScrollbar, prelude::*,};use util::ResultExt as _;use workspace::{Workspace, create_and_open_local_file};use zed_actions::{ExtensionCategoryFilter, OpenBrowser}; pub(crate) use configure_context_server_modal::ConfigureContextServerModal;pub(crate) use configure_context_server_tools_modal::ConfigureContextServerToolsModal;pub(crate) use manage_profiles_modal::ManageProfilesModal; use crate::agent_configuration::add_llm_provider_modal::{ AddLlmProviderModal, LlmCompatibleProvider,}; pub struct AgentConfiguration { fs: Arc<dyn Fs>, language_registry: Arc<LanguageRegistry>, agent_server_store: Entity<AgentServerStore>, workspace: WeakEntity<Workspace>, focus_handle: FocusHandle, configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>, context_server_store: Entity<ContextServerStore>, expanded_provider_configurations: HashMap<LanguageModelProviderId, bool>, context_server_registry: Entity<ContextServerRegistry>, _registry_subscription: Subscription, scroll_handle: ScrollHandle, _check_for_gemini: Task<()>,} impl AgentConfiguration { pub fn new( fs: Arc<dyn Fs>, agent_server_store: Entity<AgentServerStore>, context_server_store: Entity<ContextServerStore>, context_server_registry: Entity<ContextServerRegistry>, language_registry: Arc<LanguageRegistry>, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let focus_handle = cx.focus_handle(); let registry_subscription = cx.subscribe_in( &LanguageModelRegistry::global(cx), window, |this, _, event: &language_model::Event, window, cx| match event { language_model::Event::AddedProvider(provider_id) => { let provider = LanguageModelRegistry::read_global(cx).provider(provider_id); if let Some(provider) = provider { this.add_provider_configuration_view(&provider, window, cx); } } language_model::Event::RemovedProvider(provider_id) => { this.remove_provider_configuration_view(provider_id); } _ => {} }, ); cx.subscribe(&context_server_store, |_, _, _, cx| cx.notify()) .detach(); let mut this = Self { fs, language_registry, workspace, focus_handle, configuration_views_by_provider: HashMap::default(), agent_server_store, context_server_store, expanded_provider_configurations: HashMap::default(), context_server_registry, _registry_subscription: registry_subscription, scroll_handle: ScrollHandle::new(), _check_for_gemini: Task::ready(()), }; this.build_provider_configuration_views(window, cx); this } fn build_provider_configuration_views(&mut self, window: &mut Window, cx: &mut Context<Self>) { let providers = LanguageModelRegistry::read_global(cx).visible_providers(); for provider in providers { self.add_provider_configuration_view(&provider, window, cx); } } fn remove_provider_configuration_view(&mut self, provider_id: &LanguageModelProviderId) { self.configuration_views_by_provider.remove(provider_id); self.expanded_provider_configurations.remove(provider_id); } fn add_provider_configuration_view( &mut self, provider: &Arc<dyn LanguageModelProvider>, window: &mut Window, cx: &mut Context<Self>, ) { let configuration_view = provider.configuration_view( language_model::ConfigurationViewTargetAgent::ZedAgent, window, cx, ); self.configuration_views_by_provider .insert(provider.id(), configuration_view); }} impl Focusable for AgentConfiguration { fn focus_handle(&self, _: &App) -> FocusHandle { self.focus_handle.clone() }} pub enum AssistantConfigurationEvent { NewThread(Arc<dyn LanguageModelProvider>),} impl EventEmitter<AssistantConfigurationEvent> for AgentConfiguration {} enum AgentIcon { Name(IconName), Path(SharedString),} impl AgentConfiguration { fn render_section_title( &mut self, title: impl Into<SharedString>, description: impl Into<SharedString>, menu: AnyElement, ) -> impl IntoElement { h_flex() .p_4() .pb_0() .mb_2p5() .items_start() .justify_between() .child( v_flex() .w_full() .gap_0p5() .child( h_flex() .pr_1() .w_full() .gap_2() .justify_between() .flex_wrap() .child(Headline::new(title.into())) .child(menu), ) .child(Label::new(description.into()).color(Color::Muted)), ) } fn render_provider_configuration_block( &mut self, provider: &Arc<dyn LanguageModelProvider>, cx: &mut Context<Self>, ) -> impl IntoElement + use<> { let provider_id = provider.id().0; let provider_name = provider.name().0; let provider_id_string = SharedString::from(format!("provider-disclosure-{provider_id}")); let configuration_view = self .configuration_views_by_provider .get(&provider.id()) .cloned(); let is_expanded = self .expanded_provider_configurations .get(&provider.id()) .copied() .unwrap_or(false); let is_zed_provider = provider.id() == ZED_CLOUD_PROVIDER_ID; let current_plan = if is_zed_provider { self.workspace .upgrade() .and_then(|workspace| workspace.read(cx).user_store().read(cx).plan()) } else { None }; let is_signed_in = self .workspace .read_with(cx, |workspace, _| { !workspace.client().status().borrow().is_signed_out() }) .unwrap_or(false); v_flex() .min_w_0() .w_full() .when(is_expanded, |this| this.mb_2()) .child( div() .px_2() .child(Divider::horizontal().color(DividerColor::BorderFaded)), ) .child( h_flex() .map(|this| { if is_expanded { this.mt_2().mb_1() } else { this.my_2() } }) .w_full() .justify_between() .child( h_flex() .id(provider_id_string.clone()) .px_2() .py_0p5() .w_full() .justify_between() .rounded_sm() .hover(|hover| hover.bg(cx.theme().colors().element_hover)) .child( h_flex() .w_full() .gap_1p5() .child( match provider.icon() { IconOrSvg::Svg(path) => Icon::from_external_svg(path), IconOrSvg::Icon(name) => Icon::new(name), } .size(IconSize::Small) .color(Color::Muted), ) .child( h_flex() .w_full() .gap_1() .child(Label::new(provider_name.clone())) .map(|this| { if is_zed_provider && is_signed_in { this.child( self.render_zed_plan_info(current_plan, cx), ) } else { this.when( provider.is_authenticated(cx) && !is_expanded, |parent| { parent.child( Icon::new(IconName::Check) .color(Color::Success), ) }, ) } }), ), ) .child( Disclosure::new(provider_id_string, is_expanded) .opened_icon(IconName::ChevronUp) .closed_icon(IconName::ChevronDown), ) .on_click(cx.listener({ let provider_id = provider.id(); move |this, _event, _window, _cx| { let is_expanded = this .expanded_provider_configurations .entry(provider_id.clone()) .or_insert(false); *is_expanded = !*is_expanded; } })), ), ) .child( v_flex() .min_w_0() .w_full() .px_2() .gap_1() .when(is_expanded, |parent| match configuration_view { Some(configuration_view) => parent.child(configuration_view), None => parent.child(Label::new(format!( "No configuration view for {provider_name}", ))), }) .when(is_expanded && provider.is_authenticated(cx), |parent| { parent.child( Button::new( SharedString::from(format!("new-thread-{provider_id}")), "Start New Thread", ) .full_width() .style(ButtonStyle::Outlined) .layer(ElevationIndex::ModalSurface) .start_icon( Icon::new(IconName::Thread) .size(IconSize::Small) .color(Color::Muted), ) .label_size(LabelSize::Small) .on_click(cx.listener({ let provider = provider.clone(); move |_this, _event, _window, cx| { cx.emit(AssistantConfigurationEvent::NewThread( provider.clone(), )) } })), ) }) .when( is_expanded && is_removable_provider(&provider.id(), cx), |this| { this.child( Button::new( SharedString::from(format!("delete-provider-{provider_id}")), "Remove Provider", ) .full_width() .style(ButtonStyle::Outlined) .start_icon( Icon::new(IconName::Trash) .size(IconSize::Small) .color(Color::Muted), ) .label_size(LabelSize::Small) .on_click(cx.listener({ let provider = provider.clone(); move |this, _event, window, cx| { this.delete_provider(provider.clone(), window, cx); } })), ) }, ), ) } fn delete_provider( &mut self, provider: Arc<dyn LanguageModelProvider>, window: &mut Window, cx: &mut Context<Self>, ) { let fs = self.fs.clone(); let provider_id = provider.id(); cx.spawn_in(window, async move |_, cx| { cx.update(|_window, cx| { update_settings_file(fs.clone(), cx, { let provider_id = provider_id.clone(); move |settings, _| { if let Some(ref mut openai_compatible) = settings .language_models .as_mut() .and_then(|lm| lm.openai_compatible.as_mut()) { let key_to_remove: Arc<str> = Arc::from(provider_id.0.as_ref()); openai_compatible.remove(&key_to_remove); } } }); }) .log_err(); cx.update(|_window, cx| { LanguageModelRegistry::global(cx).update(cx, { let provider_id = provider_id.clone(); move |registry, cx| { registry.unregister_provider(provider_id, cx); } }) }) .log_err(); anyhow::Ok(()) }) .detach_and_log_err(cx); } fn render_provider_configuration_section( &mut self, cx: &mut Context<Self>, ) -> impl IntoElement { let providers = LanguageModelRegistry::read_global(cx).visible_providers(); let popover_menu = PopoverMenu::new("add-provider-popover") .trigger( Button::new("add-provider", "Add Provider") .style(ButtonStyle::Outlined) .start_icon( Icon::new(IconName::Plus) .size(IconSize::Small) .color(Color::Muted), ) .label_size(LabelSize::Small), ) .menu({ let workspace = self.workspace.clone(); move |window, cx| { Some(ContextMenu::build(window, cx, |menu, _window, _cx| { menu.header("Compatible APIs").entry("OpenAI", None, { let workspace = workspace.clone(); move |window, cx| { workspace .update(cx, |workspace, cx| { AddLlmProviderModal::toggle( LlmCompatibleProvider::OpenAi, workspace, window, cx, ); }) .log_err(); } }) })) } }) .anchor(gpui::Corner::TopRight) .offset(gpui::Point { x: px(0.0), y: px(2.0), }); v_flex() .min_w_0() .w_full() .child(self.render_section_title( "LLM Providers", "Add at least one provider to use AI-powered features with Zed's native agent.", popover_menu.into_any_element(), )) .child( div() .w_full() .pl(DynamicSpacing::Base08.rems(cx)) .pr(DynamicSpacing::Base20.rems(cx)) .children( providers.into_iter().map(|provider| { self.render_provider_configuration_block(&provider, cx) }), ), ) } fn render_zed_plan_info(&self, plan: Option<Plan>, cx: &mut Context<Self>) -> impl IntoElement { if let Some(plan) = plan { let free_chip_bg = cx .theme() .colors() .editor_background .opacity(0.5) .blend(cx.theme().colors().text_accent.opacity(0.05)); let pro_chip_bg = cx .theme() .colors() .editor_background .opacity(0.5)Lines 1–500 of 1462. Load more
mod add_llm_provider_modal; pub mod configure_context_server_modal; mod configure_context_server_tools_modal; mod manage_profiles_modal; mod tool_picker; use std::{ops::Range, sync::Arc}; use agent::ContextServerRegistry; use anyhow::Result; use cloud_api_types::Plan; use collections::HashMap; use context_server::ContextServerId; use editor::{Editor, MultiBufferOffset, SelectionEffects, scroll::Autoscroll}; use extension::ExtensionManifest; use extension_host::ExtensionStore; use fs::Fs; use gpui::{ Action, AnyView, App, AsyncWindowContext, Corner, Entity, EventEmitter, FocusHandle, Focusable, ScrollHandle, Subscription, Task, WeakEntity, }; use itertools::Itertools; use language::LanguageRegistry; use language_model::{ IconOrSvg, LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID, }; use language_models::AllLanguageModelSettings; use notifications::status_toast::{StatusToast, ToastIcon}; use project::{ agent_server_store::{AgentId, AgentServerStore, ExternalAgentSource}, context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore}, }; use settings::{Settings, SettingsStore, update_settings_file}; use ui::{ ButtonStyle, Chip, CommonAnimationExt, ContextMenu, ContextMenuEntry, Disclosure, Divider, DividerColor, ElevationIndex, Indicator, LabelSize, PopoverMenu, Switch, Tooltip, WithScrollbar, prelude::*, }; use util::ResultExt as _; use workspace::{Workspace, create_and_open_local_file}; use zed_actions::{ExtensionCategoryFilter, OpenBrowser}; pub(crate) use configure_context_server_modal::ConfigureContextServerModal; pub(crate) use configure_context_server_tools_modal::ConfigureContextServerToolsModal; pub(crate) use manage_profiles_modal::ManageProfilesModal; use crate::agent_configuration::add_llm_provider_modal::{ AddLlmProviderModal, LlmCompatibleProvider, }; pub struct AgentConfiguration { fs: Arc<dyn Fs>, language_registry: Arc<LanguageRegistry>, agent_server_store: Entity<AgentServerStore>, workspace: WeakEntity<Workspace>, focus_handle: FocusHandle, configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>, context_server_store: Entity<ContextServerStore>, expanded_provider_configurations: HashMap<LanguageModelProviderId, bool>, context_server_registry: Entity<ContextServerRegistry>, _registry_subscription: Subscription, scroll_handle: ScrollHandle, _check_for_gemini: Task<()>, } impl AgentConfiguration { pub fn new( fs: Arc<dyn Fs>, agent_server_store: Entity<AgentServerStore>, context_server_store: Entity<ContextServerStore>, context_server_registry: Entity<ContextServerRegistry>, language_registry: Arc<LanguageRegistry>, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let focus_handle = cx.focus_handle(); let registry_subscription = cx.subscribe_in( &LanguageModelRegistry::global(cx), window, |this, _, event: &language_model::Event, window, cx| match event { language_model::Event::AddedProvider(provider_id) => { let provider = LanguageModelRegistry::read_global(cx).provider(provider_id); if let Some(provider) = provider { this.add_provider_configuration_view(&provider, window, cx); } } language_model::Event::RemovedProvider(provider_id) => { this.remove_provider_configuration_view(provider_id); } _ => {} }, ); cx.subscribe(&context_server_store, |_, _, _, cx| cx.notify()) .detach(); let mut this = Self { fs, language_registry, workspace, focus_handle, configuration_views_by_provider: HashMap::default(), agent_server_store, context_server_store, expanded_provider_configurations: HashMap::default(), context_server_registry, _registry_subscription: registry_subscription, scroll_handle: ScrollHandle::new(), _check_for_gemini: Task::ready(()), }; this.build_provider_configuration_views(window, cx); this } fn build_provider_configuration_views(&mut self, window: &mut Window, cx: &mut Context<Self>) { let providers = LanguageModelRegistry::read_global(cx).visible_providers(); for provider in providers { self.add_provider_configuration_view(&provider, window, cx); } } fn remove_provider_configuration_view(&mut self, provider_id: &LanguageModelProviderId) { self.configuration_views_by_provider.remove(provider_id); self.expanded_provider_configurations.remove(provider_id); } fn add_provider_configuration_view( &mut self, provider: &Arc<dyn LanguageModelProvider>, window: &mut Window, cx: &mut Context<Self>, ) { let configuration_view = provider.configuration_view( language_model::ConfigurationViewTargetAgent::ZedAgent, window, cx, ); self.configuration_views_by_provider .insert(provider.id(), configuration_view); } } impl Focusable for AgentConfiguration { fn focus_handle(&self, _: &App) -> FocusHandle { self.focus_handle.clone() } } pub enum AssistantConfigurationEvent { NewThread(Arc<dyn LanguageModelProvider>), } impl EventEmitter<AssistantConfigurationEvent> for AgentConfiguration {} enum AgentIcon { Name(IconName), Path(SharedString), } impl AgentConfiguration { fn render_section_title( &mut self, title: impl Into<SharedString>, description: impl Into<SharedString>, menu: AnyElement, ) -> impl IntoElement { h_flex() .p_4() .pb_0() .mb_2p5() .items_start() .justify_between() .child( v_flex() .w_full() .gap_0p5() .child( h_flex() .pr_1() .w_full() .gap_2() .justify_between() .flex_wrap() .child(Headline::new(title.into())) .child(menu), ) .child(Label::new(description.into()).color(Color::Muted)), ) } fn render_provider_configuration_block( &mut self, provider: &Arc<dyn LanguageModelProvider>, cx: &mut Context<Self>, ) -> impl IntoElement + use<> { let provider_id = provider.id().0; let provider_name = provider.name().0; let provider_id_string = SharedString::from(format!("provider-disclosure-{provider_id}")); let configuration_view = self .configuration_views_by_provider .get(&provider.id()) .cloned(); let is_expanded = self .expanded_provider_configurations .get(&provider.id()) .copied() .unwrap_or(false); let is_zed_provider = provider.id() == ZED_CLOUD_PROVIDER_ID; let current_plan = if is_zed_provider { self.workspace .upgrade() .and_then(|workspace| workspace.read(cx).user_store().read(cx).plan()) } else { None }; let is_signed_in = self .workspace .read_with(cx, |workspace, _| { !workspace.client().status().borrow().is_signed_out() }) .unwrap_or(false); v_flex() .min_w_0() .w_full() .when(is_expanded, |this| this.mb_2()) .child( div() .px_2() .child(Divider::horizontal().color(DividerColor::BorderFaded)), ) .child( h_flex() .map(|this| { if is_expanded { this.mt_2().mb_1() } else { this.my_2() } }) .w_full() .justify_between() .child( h_flex() .id(provider_id_string.clone()) .px_2() .py_0p5() .w_full() .justify_between() .rounded_sm() .hover(|hover| hover.bg(cx.theme().colors().element_hover)) .child( h_flex() .w_full() .gap_1p5() .child( match provider.icon() { IconOrSvg::Svg(path) => Icon::from_external_svg(path), IconOrSvg::Icon(name) => Icon::new(name), } .size(IconSize::Small) .color(Color::Muted), ) .child( h_flex() .w_full() .gap_1() .child(Label::new(provider_name.clone())) .map(|this| { if is_zed_provider && is_signed_in { this.child( self.render_zed_plan_info(current_plan, cx), ) } else { this.when( provider.is_authenticated(cx) && !is_expanded, |parent| { parent.child( Icon::new(IconName::Check) .color(Color::Success), ) }, ) } }), ), ) .child( Disclosure::new(provider_id_string, is_expanded) .opened_icon(IconName::ChevronUp) .closed_icon(IconName::ChevronDown), ) .on_click(cx.listener({ let provider_id = provider.id(); move |this, _event, _window, _cx| { let is_expanded = this .expanded_provider_configurations .entry(provider_id.clone()) .or_insert(false); *is_expanded = !*is_expanded; } })), ), ) .child( v_flex() .min_w_0() .w_full() .px_2() .gap_1() .when(is_expanded, |parent| match configuration_view { Some(configuration_view) => parent.child(configuration_view), None => parent.child(Label::new(format!( "No configuration view for {provider_name}", ))), }) .when(is_expanded && provider.is_authenticated(cx), |parent| { parent.child( Button::new( SharedString::from(format!("new-thread-{provider_id}")), "Start New Thread", ) .full_width() .style(ButtonStyle::Outlined) .layer(ElevationIndex::ModalSurface) .start_icon( Icon::new(IconName::Thread) .size(IconSize::Small) .color(Color::Muted), ) .label_size(LabelSize::Small) .on_click(cx.listener({ let provider = provider.clone(); move |_this, _event, _window, cx| { cx.emit(AssistantConfigurationEvent::NewThread( provider.clone(), )) } })), ) }) .when( is_expanded && is_removable_provider(&provider.id(), cx), |this| { this.child( Button::new( SharedString::from(format!("delete-provider-{provider_id}")), "Remove Provider", ) .full_width() .style(ButtonStyle::Outlined) .start_icon( Icon::new(IconName::Trash) .size(IconSize::Small) .color(Color::Muted), ) .label_size(LabelSize::Small) .on_click(cx.listener({ let provider = provider.clone(); move |this, _event, window, cx| { this.delete_provider(provider.clone(), window, cx); } })), ) }, ), ) } fn delete_provider( &mut self, provider: Arc<dyn LanguageModelProvider>, window: &mut Window, cx: &mut Context<Self>, ) { let fs = self.fs.clone(); let provider_id = provider.id(); cx.spawn_in(window, async move |_, cx| { cx.update(|_window, cx| { update_settings_file(fs.clone(), cx, { let provider_id = provider_id.clone(); move |settings, _| { if let Some(ref mut openai_compatible) = settings .language_models .as_mut() .and_then(|lm| lm.openai_compatible.as_mut()) { let key_to_remove: Arc<str> = Arc::from(provider_id.0.as_ref()); openai_compatible.remove(&key_to_remove); } } }); }) .log_err(); cx.update(|_window, cx| { LanguageModelRegistry::global(cx).update(cx, { let provider_id = provider_id.clone(); move |registry, cx| { registry.unregister_provider(provider_id, cx); } }) }) .log_err(); anyhow::Ok(()) }) .detach_and_log_err(cx); } fn render_provider_configuration_section( &mut self, cx: &mut Context<Self>, ) -> impl IntoElement { let providers = LanguageModelRegistry::read_global(cx).visible_providers(); let popover_menu = PopoverMenu::new("add-provider-popover") .trigger( Button::new("add-provider", "Add Provider") .style(ButtonStyle::Outlined) .start_icon( Icon::new(IconName::Plus) .size(IconSize::Small) .color(Color::Muted), ) .label_size(LabelSize::Small), ) .menu({ let workspace = self.workspace.clone(); move |window, cx| { Some(ContextMenu::build(window, cx, |menu, _window, _cx| { menu.header("Compatible APIs").entry("OpenAI", None, { let workspace = workspace.clone(); move |window, cx| { workspace .update(cx, |workspace, cx| { AddLlmProviderModal::toggle( LlmCompatibleProvider::OpenAi, workspace, window, cx, ); }) .log_err(); } }) })) } }) .anchor(gpui::Corner::TopRight) .offset(gpui::Point { x: px(0.0), y: px(2.0), }); v_flex() .min_w_0() .w_full() .child(self.render_section_title( "LLM Providers", "Add at least one provider to use AI-powered features with Zed's native agent.", popover_menu.into_any_element(), )) .child( div() .w_full() .pl(DynamicSpacing::Base08.rems(cx)) .pr(DynamicSpacing::Base20.rems(cx)) .children( providers.into_iter().map(|provider| { self.render_provider_configuration_block(&provider, cx) }), ), ) } fn render_zed_plan_info(&self, plan: Option<Plan>, cx: &mut Context<Self>) -> impl IntoElement { if let Some(plan) = plan { let free_chip_bg = cx .theme() .colors() .editor_background .opacity(0.5) .blend(cx.theme().colors().text_accent.opacity(0.05)); let pro_chip_bg = cx .theme() .colors() .editor_background .opacity(0.5)