1462 lines · 60.4 KB · Rust
RawDownload
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.