From f7703973d275d99ba52af1ebe88db0cbd30daa15 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 22 Jan 2025 13:37:00 -0500 Subject: [PATCH] assistant: Extract `ConfigurationView` to its own module (#23480) This PR is a small refactoring that extracts the Assistant's `ConfigurationView` into its own module. Release Notes: - N/A --- crates/assistant/src/assistant.rs | 1 + .../assistant/src/assistant_configuration.rs | 197 +++++++++++++++++ crates/assistant/src/assistant_panel.rs | 206 +----------------- 3 files changed, 204 insertions(+), 200 deletions(-) create mode 100644 crates/assistant/src/assistant_configuration.rs diff --git a/crates/assistant/src/assistant.rs b/crates/assistant/src/assistant.rs index f01763e876..7c1e5b8406 100644 --- a/crates/assistant/src/assistant.rs +++ b/crates/assistant/src/assistant.rs @@ -1,5 +1,6 @@ #![cfg_attr(target_os = "windows", allow(unused, dead_code))] +mod assistant_configuration; pub mod assistant_panel; mod inline_assistant; pub mod slash_command_settings; diff --git a/crates/assistant/src/assistant_configuration.rs b/crates/assistant/src/assistant_configuration.rs new file mode 100644 index 0000000000..e4756e1e86 --- /dev/null +++ b/crates/assistant/src/assistant_configuration.rs @@ -0,0 +1,197 @@ +use std::sync::Arc; + +use collections::HashMap; +use gpui::{canvas, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView, Subscription}; +use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry}; +use ui::{prelude::*, ElevationIndex}; +use workspace::Item; + +pub struct ConfigurationView { + focus_handle: FocusHandle, + configuration_views: HashMap, + _registry_subscription: Subscription, +} + +impl ConfigurationView { + pub fn new(cx: &mut ViewContext) -> Self { + let focus_handle = cx.focus_handle(); + + let registry_subscription = cx.subscribe( + &LanguageModelRegistry::global(cx), + |this, _, event: &language_model::Event, 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_configuration_view(&provider, cx); + } + } + language_model::Event::RemovedProvider(provider_id) => { + this.remove_configuration_view(provider_id); + } + _ => {} + }, + ); + + let mut this = Self { + focus_handle, + configuration_views: HashMap::default(), + _registry_subscription: registry_subscription, + }; + this.build_configuration_views(cx); + this + } + + fn build_configuration_views(&mut self, cx: &mut ViewContext) { + let providers = LanguageModelRegistry::read_global(cx).providers(); + for provider in providers { + self.add_configuration_view(&provider, cx); + } + } + + fn remove_configuration_view(&mut self, provider_id: &LanguageModelProviderId) { + self.configuration_views.remove(provider_id); + } + + fn add_configuration_view( + &mut self, + provider: &Arc, + cx: &mut ViewContext, + ) { + let configuration_view = provider.configuration_view(cx); + self.configuration_views + .insert(provider.id(), configuration_view); + } + + fn render_provider_view( + &mut self, + provider: &Arc, + cx: &mut ViewContext, + ) -> Div { + let provider_id = provider.id().0.clone(); + let provider_name = provider.name().0.clone(); + let configuration_view = self.configuration_views.get(&provider.id()).cloned(); + + let open_new_context = cx.listener({ + let provider = provider.clone(); + move |_, _, cx| { + cx.emit(ConfigurationViewEvent::NewProviderContextEditor( + provider.clone(), + )) + } + }); + + v_flex() + .gap_2() + .child( + h_flex() + .justify_between() + .child(Headline::new(provider_name.clone()).size(HeadlineSize::Small)) + .when(provider.is_authenticated(cx), move |this| { + this.child( + h_flex().justify_end().child( + Button::new( + SharedString::from(format!("new-context-{provider_id}")), + "Open New Chat", + ) + .icon_position(IconPosition::Start) + .icon(IconName::Plus) + .style(ButtonStyle::Filled) + .layer(ElevationIndex::ModalSurface) + .on_click(open_new_context), + ), + ) + }), + ) + .child( + div() + .p(DynamicSpacing::Base08.rems(cx)) + .bg(cx.theme().colors().surface_background) + .border_1() + .border_color(cx.theme().colors().border_variant) + .rounded_md() + .when(configuration_view.is_none(), |this| { + this.child(div().child(Label::new(format!( + "No configuration view for {}", + provider_name + )))) + }) + .when_some(configuration_view, |this, configuration_view| { + this.child(configuration_view) + }), + ) + } +} + +impl Render for ConfigurationView { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let providers = LanguageModelRegistry::read_global(cx).providers(); + let provider_views = providers + .into_iter() + .map(|provider| self.render_provider_view(&provider, cx)) + .collect::>(); + + let mut element = v_flex() + .id("assistant-configuration-view") + .track_focus(&self.focus_handle(cx)) + .bg(cx.theme().colors().editor_background) + .size_full() + .overflow_y_scroll() + .child( + v_flex() + .p(DynamicSpacing::Base16.rems(cx)) + .border_b_1() + .border_color(cx.theme().colors().border) + .gap_1() + .child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium)) + .child( + Label::new( + "At least one LLM provider must be configured to use the Assistant.", + ) + .color(Color::Muted), + ), + ) + .child( + v_flex() + .p(DynamicSpacing::Base16.rems(cx)) + .mt_1() + .gap_6() + .flex_1() + .children(provider_views), + ) + .into_any(); + + // We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround + // because we couldn't the element to take up the size of the parent. + canvas( + move |bounds, cx| { + element.prepaint_as_root(bounds.origin, bounds.size.into(), cx); + element + }, + |_, mut element, cx| { + element.paint(cx); + }, + ) + .flex_1() + .w_full() + } +} + +pub enum ConfigurationViewEvent { + NewProviderContextEditor(Arc), +} + +impl EventEmitter for ConfigurationView {} + +impl FocusableView for ConfigurationView { + fn focus_handle(&self, _: &AppContext) -> FocusHandle { + self.focus_handle.clone() + } +} + +impl Item for ConfigurationView { + type Event = ConfigurationViewEvent; + + fn tab_content_text(&self, _cx: &WindowContext) -> Option { + Some("Configuration".into()) + } +} diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index ccafd3eca9..ede925a960 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -1,3 +1,4 @@ +use crate::assistant_configuration::{ConfigurationView, ConfigurationViewEvent}; use crate::{ terminal_inline_assistant::TerminalInlineAssistant, DeployHistory, DeployPromptLibrary, InlineAssistant, NewContext, @@ -13,19 +14,15 @@ use assistant_settings::{AssistantDockPosition, AssistantSettings}; use assistant_slash_command::SlashCommandWorkingSet; use assistant_tool::ToolWorkingSet; use client::{proto, Client, Status}; -use collections::HashMap; use editor::{Editor, EditorEvent}; use fs::Fs; use gpui::{ - canvas, div, prelude::*, Action, AnyView, AppContext, AsyncWindowContext, EventEmitter, - ExternalPaths, FocusHandle, FocusableView, InteractiveElement, IntoElement, Model, - ParentElement, Pixels, Render, SharedString, StatefulInteractiveElement, Styled, Subscription, - Task, UpdateGlobal, View, WeakView, + prelude::*, Action, AppContext, AsyncWindowContext, EventEmitter, ExternalPaths, FocusHandle, + FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Pixels, Render, Styled, + Subscription, Task, UpdateGlobal, View, WeakView, }; use language::LanguageRegistry; -use language_model::{ - LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID, -}; +use language_model::{LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID}; use language_model_selector::LanguageModelSelector; use project::Project; use prompt_library::{open_prompt_library, PromptBuilder, PromptLibrary}; @@ -34,12 +31,11 @@ use settings::{update_settings_file, Settings}; use smol::stream::StreamExt; use std::{ops::ControlFlow, path::PathBuf, sync::Arc}; use terminal_view::{terminal_panel::TerminalPanel, TerminalView}; -use ui::{prelude::*, ContextMenu, ElevationIndex, PopoverMenu, PopoverMenuHandle, Tooltip}; +use ui::{prelude::*, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip}; use util::{maybe, ResultExt}; use workspace::DraggedTab; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, - item::Item, pane, DraggedSelection, Pane, ShowConfiguration, ToggleZoom, Workspace, }; use zed_actions::assistant::{InlineAssist, ToggleFocus}; @@ -1371,193 +1367,3 @@ pub enum WorkflowAssistStatus { Done, Idle, } - -pub struct ConfigurationView { - focus_handle: FocusHandle, - configuration_views: HashMap, - _registry_subscription: Subscription, -} - -impl ConfigurationView { - fn new(cx: &mut ViewContext) -> Self { - let focus_handle = cx.focus_handle(); - - let registry_subscription = cx.subscribe( - &LanguageModelRegistry::global(cx), - |this, _, event: &language_model::Event, 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_configuration_view(&provider, cx); - } - } - language_model::Event::RemovedProvider(provider_id) => { - this.remove_configuration_view(provider_id); - } - _ => {} - }, - ); - - let mut this = Self { - focus_handle, - configuration_views: HashMap::default(), - _registry_subscription: registry_subscription, - }; - this.build_configuration_views(cx); - this - } - - fn build_configuration_views(&mut self, cx: &mut ViewContext) { - let providers = LanguageModelRegistry::read_global(cx).providers(); - for provider in providers { - self.add_configuration_view(&provider, cx); - } - } - - fn remove_configuration_view(&mut self, provider_id: &LanguageModelProviderId) { - self.configuration_views.remove(provider_id); - } - - fn add_configuration_view( - &mut self, - provider: &Arc, - cx: &mut ViewContext, - ) { - let configuration_view = provider.configuration_view(cx); - self.configuration_views - .insert(provider.id(), configuration_view); - } - - fn render_provider_view( - &mut self, - provider: &Arc, - cx: &mut ViewContext, - ) -> Div { - let provider_id = provider.id().0.clone(); - let provider_name = provider.name().0.clone(); - let configuration_view = self.configuration_views.get(&provider.id()).cloned(); - - let open_new_context = cx.listener({ - let provider = provider.clone(); - move |_, _, cx| { - cx.emit(ConfigurationViewEvent::NewProviderContextEditor( - provider.clone(), - )) - } - }); - - v_flex() - .gap_2() - .child( - h_flex() - .justify_between() - .child(Headline::new(provider_name.clone()).size(HeadlineSize::Small)) - .when(provider.is_authenticated(cx), move |this| { - this.child( - h_flex().justify_end().child( - Button::new( - SharedString::from(format!("new-context-{provider_id}")), - "Open New Chat", - ) - .icon_position(IconPosition::Start) - .icon(IconName::Plus) - .style(ButtonStyle::Filled) - .layer(ElevationIndex::ModalSurface) - .on_click(open_new_context), - ), - ) - }), - ) - .child( - div() - .p(DynamicSpacing::Base08.rems(cx)) - .bg(cx.theme().colors().surface_background) - .border_1() - .border_color(cx.theme().colors().border_variant) - .rounded_md() - .when(configuration_view.is_none(), |this| { - this.child(div().child(Label::new(format!( - "No configuration view for {}", - provider_name - )))) - }) - .when_some(configuration_view, |this, configuration_view| { - this.child(configuration_view) - }), - ) - } -} - -impl Render for ConfigurationView { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let providers = LanguageModelRegistry::read_global(cx).providers(); - let provider_views = providers - .into_iter() - .map(|provider| self.render_provider_view(&provider, cx)) - .collect::>(); - - let mut element = v_flex() - .id("assistant-configuration-view") - .track_focus(&self.focus_handle(cx)) - .bg(cx.theme().colors().editor_background) - .size_full() - .overflow_y_scroll() - .child( - v_flex() - .p(DynamicSpacing::Base16.rems(cx)) - .border_b_1() - .border_color(cx.theme().colors().border) - .gap_1() - .child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium)) - .child( - Label::new( - "At least one LLM provider must be configured to use the Assistant.", - ) - .color(Color::Muted), - ), - ) - .child( - v_flex() - .p(DynamicSpacing::Base16.rems(cx)) - .mt_1() - .gap_6() - .flex_1() - .children(provider_views), - ) - .into_any(); - - // We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround - // because we couldn't the element to take up the size of the parent. - canvas( - move |bounds, cx| { - element.prepaint_as_root(bounds.origin, bounds.size.into(), cx); - element - }, - |_, mut element, cx| { - element.paint(cx); - }, - ) - .flex_1() - .w_full() - } -} - -pub enum ConfigurationViewEvent { - NewProviderContextEditor(Arc), -} - -impl EventEmitter for ConfigurationView {} - -impl FocusableView for ConfigurationView { - fn focus_handle(&self, _: &AppContext) -> FocusHandle { - self.focus_handle.clone() - } -} - -impl Item for ConfigurationView { - type Event = ConfigurationViewEvent; - - fn tab_content_text(&self, _cx: &WindowContext) -> Option { - Some("Configuration".into()) - } -}