assistant2: Add configuration (#23481)

This PR wires up the ability to configure Assistant2.

<img width="1309" alt="Screenshot 2025-01-22 at 1 52 56 PM"
src="https://github.com/user-attachments/assets/3de47797-7959-47af-bd93-51f105e87c28"
/>

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2025-01-22 14:07:48 -05:00 committed by GitHub
parent f7703973d2
commit 514d9b4161
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 229 additions and 6 deletions

View file

@ -1,4 +1,5 @@
mod active_thread;
mod assistant_configuration;
mod assistant_model_selector;
mod assistant_panel;
mod buffer_codegen;
@ -41,6 +42,7 @@ actions!(
RemoveAllContext,
OpenHistory,
OpenPromptEditorHistory,
OpenConfiguration,
RemoveSelectedThread,
Chat,
ChatMode,

View file

@ -0,0 +1,159 @@
use std::sync::Arc;
use collections::HashMap;
use gpui::{AnyView, AppContext, EventEmitter, FocusHandle, FocusableView, Subscription};
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
use ui::{prelude::*, ElevationIndex};
pub struct AssistantConfiguration {
focus_handle: FocusHandle,
configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>,
_registry_subscription: Subscription,
}
impl AssistantConfiguration {
pub fn new(cx: &mut ViewContext<Self>) -> 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_provider_configuration_view(&provider, cx);
}
}
language_model::Event::RemovedProvider(provider_id) => {
this.remove_provider_configuration_view(provider_id);
}
_ => {}
},
);
let mut this = Self {
focus_handle,
configuration_views_by_provider: HashMap::default(),
_registry_subscription: registry_subscription,
};
this.build_provider_configuration_views(cx);
this
}
fn build_provider_configuration_views(&mut self, cx: &mut ViewContext<Self>) {
let providers = LanguageModelRegistry::read_global(cx).providers();
for provider in providers {
self.add_provider_configuration_view(&provider, cx);
}
}
fn remove_provider_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
self.configuration_views_by_provider.remove(provider_id);
}
fn add_provider_configuration_view(
&mut self,
provider: &Arc<dyn LanguageModelProvider>,
cx: &mut ViewContext<Self>,
) {
let configuration_view = provider.configuration_view(cx);
self.configuration_views_by_provider
.insert(provider.id(), configuration_view);
}
}
impl FocusableView for AssistantConfiguration {
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
self.focus_handle.clone()
}
}
pub enum AssistantConfigurationEvent {
NewThread(Arc<dyn LanguageModelProvider>),
}
impl EventEmitter<AssistantConfigurationEvent> for AssistantConfiguration {}
impl AssistantConfiguration {
fn render_provider_configuration(
&mut self,
provider: &Arc<dyn LanguageModelProvider>,
cx: &mut ViewContext<Self>,
) -> impl IntoElement {
let provider_id = provider.id().0.clone();
let provider_name = provider.name().0.clone();
let configuration_view = self
.configuration_views_by_provider
.get(&provider.id())
.cloned();
v_flex()
.gap_2()
.child(
h_flex()
.justify_between()
.child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
.when(provider.is_authenticated(cx), |parent| {
parent.child(
h_flex().justify_end().child(
Button::new(
SharedString::from(format!("new-thread-{provider_id}")),
"Open New Thread",
)
.icon_position(IconPosition::Start)
.icon(IconName::Plus)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.on_click(cx.listener({
let provider = provider.clone();
move |_this, _event, cx| {
cx.emit(AssistantConfigurationEvent::NewThread(
provider.clone(),
))
}
})),
),
)
}),
)
.child(
div()
.p(DynamicSpacing::Base08.rems(cx))
.bg(cx.theme().colors().surface_background)
.border_1()
.border_color(cx.theme().colors().border_variant)
.rounded_md()
.map(|parent| match configuration_view {
Some(configuration_view) => parent.child(configuration_view),
None => parent.child(div().child(Label::new(format!(
"No configuration view for {provider_name}",
)))),
}),
)
}
}
impl Render for AssistantConfiguration {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let providers = LanguageModelRegistry::read_global(cx).providers();
v_flex()
.id("assistant-configuration")
.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))
.mt_1()
.gap_6()
.flex_1()
.children(
providers
.into_iter()
.map(|provider| self.render_provider_configuration(&provider, cx)),
),
)
}
}

View file

@ -12,13 +12,14 @@ use client::zed_urls;
use fs::Fs;
use gpui::{
prelude::*, px, svg, Action, AnyElement, AppContext, AsyncWindowContext, Corner, EventEmitter,
FocusHandle, FocusableView, FontWeight, Model, Pixels, Task, View, ViewContext, WeakView,
WindowContext,
FocusHandle, FocusableView, FontWeight, Model, Pixels, Subscription, Task, View, ViewContext,
WeakView, WindowContext,
};
use language::LanguageRegistry;
use language_model::LanguageModelRegistry;
use project::Project;
use prompt_library::PromptBuilder;
use settings::Settings;
use settings::{update_settings_file, Settings};
use time::UtcOffset;
use ui::{prelude::*, ContextMenu, KeyBinding, PopoverMenu, PopoverMenuHandle, Tab, Tooltip};
use util::ResultExt as _;
@ -27,11 +28,12 @@ use workspace::Workspace;
use zed_actions::assistant::ToggleFocus;
use crate::active_thread::ActiveThread;
use crate::assistant_configuration::{AssistantConfiguration, AssistantConfigurationEvent};
use crate::message_editor::MessageEditor;
use crate::thread::{Thread, ThreadError, ThreadId};
use crate::thread_history::{PastThread, ThreadHistory};
use crate::thread_store::ThreadStore;
use crate::{NewPromptEditor, NewThread, OpenHistory, OpenPromptEditorHistory};
use crate::{NewPromptEditor, NewThread, OpenConfiguration, OpenHistory, OpenPromptEditorHistory};
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(
@ -60,6 +62,12 @@ pub fn init(cx: &mut AppContext) {
workspace.focus_panel::<AssistantPanel>(cx);
panel.update(cx, |panel, cx| panel.open_prompt_editor_history(cx));
}
})
.register_action(|workspace, _: &OpenConfiguration, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
workspace.focus_panel::<AssistantPanel>(cx);
panel.update(cx, |panel, cx| panel.open_configuration(cx));
}
});
},
)
@ -71,6 +79,7 @@ enum ActiveView {
PromptEditor,
History,
PromptEditorHistory,
Configuration,
}
pub struct AssistantPanel {
@ -84,6 +93,8 @@ pub struct AssistantPanel {
context_store: Model<assistant_context_editor::ContextStore>,
context_editor: Option<View<ContextEditor>>,
context_history: Option<View<ContextHistory>>,
configuration: Option<View<AssistantConfiguration>>,
configuration_subscription: Option<Subscription>,
tools: Arc<ToolWorkingSet>,
local_timezone: UtcOffset,
active_view: ActiveView,
@ -173,6 +184,8 @@ impl AssistantPanel {
context_store,
context_editor: None,
context_history: None,
configuration: None,
configuration_subscription: None,
tools,
local_timezone: UtcOffset::from_whole_seconds(
chrono::Local::now().offset().local_minus_utc(),
@ -357,6 +370,46 @@ impl AssistantPanel {
self.message_editor.focus_handle(cx).focus(cx);
}
pub(crate) fn open_configuration(&mut self, cx: &mut ViewContext<Self>) {
self.active_view = ActiveView::Configuration;
self.configuration = Some(cx.new_view(AssistantConfiguration::new));
if let Some(configuration) = self.configuration.as_ref() {
self.configuration_subscription =
Some(cx.subscribe(configuration, Self::handle_assistant_configuration_event));
configuration.focus_handle(cx).focus(cx);
}
}
fn handle_assistant_configuration_event(
&mut self,
_view: View<AssistantConfiguration>,
event: &AssistantConfigurationEvent,
cx: &mut ViewContext<Self>,
) {
match event {
AssistantConfigurationEvent::NewThread(provider) => {
if LanguageModelRegistry::read_global(cx)
.active_provider()
.map_or(true, |active_provider| {
active_provider.id() != provider.id()
})
{
if let Some(model) = provider.provided_models(cx).first().cloned() {
update_settings_file::<AssistantSettings>(
self.fs.clone(),
cx,
move |settings, _| settings.set_model(model),
);
}
}
self.new_thread(cx);
}
}
}
pub(crate) fn active_thread(&self, cx: &AppContext) -> Model<Thread> {
self.thread.read(cx).thread.clone()
}
@ -386,6 +439,13 @@ impl FocusableView for AssistantPanel {
cx.focus_handle()
}
}
ActiveView::Configuration => {
if let Some(configuration) = self.configuration.as_ref() {
configuration.focus_handle(cx)
} else {
cx.focus_handle()
}
}
}
}
}
@ -493,6 +553,7 @@ impl AssistantPanel {
.unwrap_or_else(|| SharedString::from("Loading Summary…")),
ActiveView::History => "History / Thread".into(),
ActiveView::PromptEditorHistory => "History / Prompt Editor".into(),
ActiveView::Configuration => "Configuration".into(),
};
h_flex()
@ -555,8 +616,8 @@ impl AssistantPanel {
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.tooltip(move |cx| Tooltip::text("Configure Assistant", cx))
.on_click(move |_event, _cx| {
println!("Configure Assistant");
.on_click(move |_event, cx| {
cx.dispatch_action(OpenConfiguration.boxed_clone());
}),
),
)
@ -810,6 +871,7 @@ impl Render for AssistantPanel {
ActiveView::History => parent.child(self.history.clone()),
ActiveView::PromptEditor => parent.children(self.context_editor.clone()),
ActiveView::PromptEditorHistory => parent.children(self.context_history.clone()),
ActiveView::Configuration => parent.children(self.configuration.clone()),
})
}
}