Detailed changes
@@ -35,7 +35,7 @@ use language_model::{
report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelTextStream, Role,
};
-use language_model_selector::inline_language_model_selector;
+use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use multi_buffer::MultiBufferRow;
use parking_lot::Mutex;
use project::{CodeAction, ProjectTransaction};
@@ -1425,6 +1425,7 @@ enum PromptEditorEvent {
struct PromptEditor {
id: InlineAssistId,
editor: Entity<Editor>,
+ language_model_selector: Entity<LanguageModelSelector>,
edited_since_done: bool,
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
prompt_history: VecDeque<String>,
@@ -1438,7 +1439,6 @@ struct PromptEditor {
_token_count_subscriptions: Vec<Subscription>,
workspace: Option<WeakEntity<Workspace>>,
show_rate_limit_notice: bool,
- fs: Arc<dyn Fs>,
}
#[derive(Copy, Clone)]
@@ -1589,16 +1589,29 @@ impl Render for PromptEditor {
.w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
.justify_center()
.gap_2()
- .child(inline_language_model_selector({
- let fs = self.fs.clone();
- move |model, cx| {
- update_settings_file::<AssistantSettings>(
- fs.clone(),
+ .child(LanguageModelSelectorPopoverMenu::new(
+ self.language_model_selector.clone(),
+ IconButton::new("context", IconName::SettingsAlt)
+ .shape(IconButtonShape::Square)
+ .icon_size(IconSize::Small)
+ .icon_color(Color::Muted),
+ move |window, cx| {
+ Tooltip::with_meta(
+ format!(
+ "Using {}",
+ LanguageModelRegistry::read_global(cx)
+ .active_model()
+ .map(|model| model.name().0)
+ .unwrap_or_else(|| "No model selected".into()),
+ ),
+ None,
+ "Change Model",
+ window,
cx,
- move |settings, _| settings.set_model(model.clone()),
- );
- }
- }))
+ )
+ },
+ gpui::Corner::TopRight,
+ ))
.map(|el| {
let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else {
return el;
@@ -1711,8 +1724,21 @@ impl PromptEditor {
let mut this = Self {
id,
- fs,
editor: prompt_editor,
+ language_model_selector: cx.new(|cx| {
+ let fs = fs.clone();
+ LanguageModelSelector::new(
+ move |model, cx| {
+ update_settings_file::<AssistantSettings>(
+ fs.clone(),
+ cx,
+ move |settings, _| settings.set_model(model.clone()),
+ );
+ },
+ window,
+ cx,
+ )
+ }),
edited_since_done: false,
gutter_dimensions,
prompt_history,
@@ -19,7 +19,7 @@ use language_model::{
report_assistant_event, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, Role,
};
-use language_model_selector::inline_language_model_selector;
+use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use prompt_store::PromptBuilder;
use settings::{update_settings_file, Settings};
use std::{
@@ -487,9 +487,9 @@ enum PromptEditorEvent {
struct PromptEditor {
id: TerminalInlineAssistId,
- fs: Arc<dyn Fs>,
height_in_lines: u8,
editor: Entity<Editor>,
+ language_model_selector: Entity<LanguageModelSelector>,
edited_since_done: bool,
prompt_history: VecDeque<String>,
prompt_history_ix: Option<usize>,
@@ -641,16 +641,29 @@ impl Render for PromptEditor {
.w_12()
.justify_center()
.gap_2()
- .child(inline_language_model_selector({
- let fs = self.fs.clone();
- move |model, cx| {
- update_settings_file::<AssistantSettings>(
- fs.clone(),
+ .child(LanguageModelSelectorPopoverMenu::new(
+ self.language_model_selector.clone(),
+ IconButton::new("change-model", IconName::SettingsAlt)
+ .shape(IconButtonShape::Square)
+ .icon_size(IconSize::Small)
+ .icon_color(Color::Muted),
+ move |window, cx| {
+ Tooltip::with_meta(
+ format!(
+ "Using {}",
+ LanguageModelRegistry::read_global(cx)
+ .active_model()
+ .map(|model| model.name().0)
+ .unwrap_or_else(|| "No model selected".into()),
+ ),
+ None,
+ "Change Model",
+ window,
cx,
- move |settings, _| settings.set_model(model.clone()),
- );
- }
- }))
+ )
+ },
+ gpui::Corner::TopRight,
+ ))
.children(
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {
let error_message = SharedString::from(error.to_string());
@@ -728,9 +741,22 @@ impl PromptEditor {
let mut this = Self {
id,
- fs,
height_in_lines: 1,
editor: prompt_editor,
+ language_model_selector: cx.new(|cx| {
+ let fs = fs.clone();
+ LanguageModelSelector::new(
+ move |model, cx| {
+ update_settings_file::<AssistantSettings>(
+ fs.clone(),
+ cx,
+ move |settings, _| settings.set_model(model.clone()),
+ );
+ },
+ window,
+ cx,
+ )
+ }),
edited_since_done: false,
prompt_history,
prompt_history_ix: None,
@@ -1,28 +1,45 @@
use assistant_settings::AssistantSettings;
use fs::Fs;
-use gpui::FocusHandle;
-use language_model_selector::{assistant_language_model_selector, LanguageModelSelector};
+use gpui::{Entity, FocusHandle, SharedString};
+use language_model::LanguageModelRegistry;
+use language_model_selector::{
+ LanguageModelSelector, LanguageModelSelectorPopoverMenu, ToggleModelSelector,
+};
use settings::update_settings_file;
use std::sync::Arc;
-use ui::{prelude::*, PopoverMenuHandle};
+use ui::{prelude::*, ButtonLike, PopoverMenuHandle, Tooltip};
pub struct AssistantModelSelector {
+ selector: Entity<LanguageModelSelector>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
focus_handle: FocusHandle,
- fs: Arc<dyn Fs>,
}
impl AssistantModelSelector {
pub(crate) fn new(
fs: Arc<dyn Fs>,
+ menu_handle: PopoverMenuHandle<LanguageModelSelector>,
focus_handle: FocusHandle,
- _window: &mut Window,
- _cx: &mut App,
+ window: &mut Window,
+ cx: &mut App,
) -> Self {
Self {
- fs,
+ selector: cx.new(|cx| {
+ let fs = fs.clone();
+ LanguageModelSelector::new(
+ move |model, cx| {
+ update_settings_file::<AssistantSettings>(
+ fs.clone(),
+ cx,
+ move |settings, _cx| settings.set_model(model.clone()),
+ );
+ },
+ window,
+ cx,
+ )
+ }),
+ menu_handle,
focus_handle,
- menu_handle: PopoverMenuHandle::default(),
}
}
@@ -32,21 +49,43 @@ impl AssistantModelSelector {
}
impl Render for AssistantModelSelector {
- fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- assistant_language_model_selector(
- self.focus_handle.clone(),
- Some(self.menu_handle.clone()),
- cx,
- {
- let fs = self.fs.clone();
- move |model, cx| {
- update_settings_file::<AssistantSettings>(
- fs.clone(),
- cx,
- move |settings, _| settings.set_model(model.clone()),
- );
- }
+ fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+ let active_model = LanguageModelRegistry::read_global(cx).active_model();
+ let focus_handle = self.focus_handle.clone();
+ let model_name = match active_model {
+ Some(model) => model.name().0,
+ _ => SharedString::from("No model selected"),
+ };
+
+ LanguageModelSelectorPopoverMenu::new(
+ self.selector.clone(),
+ ButtonLike::new("active-model")
+ .style(ButtonStyle::Subtle)
+ .child(
+ h_flex()
+ .gap_0p5()
+ .child(
+ Label::new(model_name)
+ .size(LabelSize::Small)
+ .color(Color::Muted),
+ )
+ .child(
+ Icon::new(IconName::ChevronDown)
+ .color(Color::Muted)
+ .size(IconSize::XSmall),
+ ),
+ ),
+ move |window, cx| {
+ Tooltip::for_action_in(
+ "Change Model",
+ &ToggleModelSelector,
+ &focus_handle,
+ window,
+ cx,
+ )
},
+ gpui::Corner::BottomRight,
)
+ .with_handle(self.menu_handle.clone())
}
}
@@ -857,6 +857,7 @@ impl PromptEditor<BufferCodegen> {
editor
});
let context_picker_menu_handle = PopoverMenuHandle::default();
+ let model_selector_menu_handle = PopoverMenuHandle::default();
let context_strip = cx.new(|cx| {
ContextStrip::new(
@@ -880,7 +881,13 @@ impl PromptEditor<BufferCodegen> {
context_strip,
context_picker_menu_handle,
model_selector: cx.new(|cx| {
- AssistantModelSelector::new(fs, prompt_editor.focus_handle(cx), window, cx)
+ AssistantModelSelector::new(
+ fs,
+ model_selector_menu_handle,
+ prompt_editor.focus_handle(cx),
+ window,
+ cx,
+ )
}),
edited_since_done: false,
prompt_history,
@@ -1005,6 +1012,7 @@ impl PromptEditor<TerminalCodegen> {
editor
});
let context_picker_menu_handle = PopoverMenuHandle::default();
+ let model_selector_menu_handle = PopoverMenuHandle::default();
let context_strip = cx.new(|cx| {
ContextStrip::new(
@@ -1028,7 +1036,13 @@ impl PromptEditor<TerminalCodegen> {
context_strip,
context_picker_menu_handle,
model_selector: cx.new(|cx| {
- AssistantModelSelector::new(fs, prompt_editor.focus_handle(cx), window, cx)
+ AssistantModelSelector::new(
+ fs,
+ model_selector_menu_handle.clone(),
+ prompt_editor.focus_handle(cx),
+ window,
+ cx,
+ )
}),
edited_since_done: false,
prompt_history,
@@ -54,6 +54,7 @@ impl MessageEditor {
let context_store = cx.new(|_cx| ContextStore::new(workspace.clone()));
let context_picker_menu_handle = PopoverMenuHandle::default();
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
+ let model_selector_menu_handle = PopoverMenuHandle::default();
let editor = cx.new(|cx| {
let mut editor = Editor::auto_height(10, window, cx);
@@ -106,8 +107,15 @@ impl MessageEditor {
context_picker_menu_handle,
inline_context_picker,
inline_context_picker_menu_handle,
- model_selector: cx
- .new(|cx| AssistantModelSelector::new(fs, editor.focus_handle(cx), window, cx)),
+ model_selector: cx.new(|cx| {
+ AssistantModelSelector::new(
+ fs,
+ model_selector_menu_handle,
+ editor.focus_handle(cx),
+ window,
+ cx,
+ )
+ }),
use_tools: false,
_subscriptions: subscriptions,
}
@@ -38,7 +38,7 @@ use language_model::{
Role,
};
use language_model_selector::{
- assistant_language_model_selector, LanguageModelSelector, ToggleModelSelector,
+ LanguageModelSelector, LanguageModelSelectorPopoverMenu, ToggleModelSelector,
};
use multi_buffer::MultiBufferRow;
use picker::Picker;
@@ -197,7 +197,8 @@ pub struct ContextEditor {
// the file is opened. In order to keep the worktree alive for the duration of the
// context editor, we keep a reference here.
dragged_file_worktrees: Vec<Entity<Worktree>>,
- language_model_selector: PopoverMenuHandle<LanguageModelSelector>,
+ language_model_selector: Entity<LanguageModelSelector>,
+ language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
}
pub const DEFAULT_TAB_TITLE: &str = "New Chat";
@@ -263,7 +264,7 @@ impl ContextEditor {
image_blocks: Default::default(),
scroll_position: None,
remote_id: None,
- fs,
+ fs: fs.clone(),
workspace,
project,
pending_slash_command_creases: HashMap::default(),
@@ -275,7 +276,20 @@ impl ContextEditor {
show_accept_terms: false,
slash_menu_handle: Default::default(),
dragged_file_worktrees: Vec::new(),
- language_model_selector: PopoverMenuHandle::default(),
+ language_model_selector: cx.new(|cx| {
+ LanguageModelSelector::new(
+ move |model, cx| {
+ update_settings_file::<AssistantSettings>(
+ fs.clone(),
+ cx,
+ move |settings, _| settings.set_model(model.clone()),
+ );
+ },
+ window,
+ cx,
+ )
+ }),
+ language_model_selector_menu_handle: PopoverMenuHandle::default(),
};
this.update_message_headers(cx);
this.update_image_blocks(cx);
@@ -2375,6 +2389,46 @@ impl ContextEditor {
)
}
+ fn render_language_model_selector(&self, cx: &mut Context<Self>) -> impl IntoElement {
+ let active_model = LanguageModelRegistry::read_global(cx).active_model();
+ let focus_handle = self.editor().focus_handle(cx).clone();
+ let model_name = match active_model {
+ Some(model) => model.name().0,
+ None => SharedString::from("No model selected"),
+ };
+
+ LanguageModelSelectorPopoverMenu::new(
+ self.language_model_selector.clone(),
+ ButtonLike::new("active-model")
+ .style(ButtonStyle::Subtle)
+ .child(
+ h_flex()
+ .gap_0p5()
+ .child(
+ Label::new(model_name)
+ .size(LabelSize::Small)
+ .color(Color::Muted),
+ )
+ .child(
+ Icon::new(IconName::ChevronDown)
+ .color(Color::Muted)
+ .size(IconSize::XSmall),
+ ),
+ ),
+ move |window, cx| {
+ Tooltip::for_action_in(
+ "Change Model",
+ &ToggleModelSelector,
+ &focus_handle,
+ window,
+ cx,
+ )
+ },
+ gpui::Corner::BottomLeft,
+ )
+ .with_handle(self.language_model_selector_menu_handle.clone())
+ }
+
fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
let last_error = self.last_error.as_ref()?;
@@ -2819,7 +2873,7 @@ impl Render for ContextEditor {
None
};
- let language_model_selector = self.language_model_selector.clone();
+ let language_model_selector = self.language_model_selector_menu_handle.clone();
v_flex()
.key_context("ContextEditor")
.capture_action(cx.listener(ContextEditor::cancel))
@@ -2872,23 +2926,11 @@ impl Render for ContextEditor {
.gap_1()
.child(self.render_inject_context_menu(cx))
.child(ui::Divider::vertical())
- .child(div().pl_0p5().child(assistant_language_model_selector(
- self.editor().focus_handle(cx),
- Some(self.language_model_selector.clone()),
- cx,
- {
- let fs = self.fs.clone();
- move |model, cx| {
- update_settings_file::<AssistantSettings>(
- fs.clone(),
- cx,
- move |settings, _| {
- settings.set_model(model.clone())
- },
- );
- }
- },
- ))),
+ .child(
+ div()
+ .pl_0p5()
+ .child(self.render_language_model_selector(cx)),
+ ),
)
.child(
h_flex()
@@ -1,8 +1,8 @@
-use std::{rc::Rc, sync::Arc};
+use std::sync::Arc;
use feature_flags::ZedPro;
use gpui::{
- action_with_deprecated_aliases, Action, AnyElement, App, Corner, DismissEvent, Entity,
+ action_with_deprecated_aliases, Action, AnyElement, AnyView, App, Corner, DismissEvent, Entity,
EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity,
};
use language_model::{
@@ -10,10 +10,7 @@ use language_model::{
};
use picker::{Picker, PickerDelegate};
use proto::Plan;
-use ui::{
- prelude::*, ButtonLike, IconButtonShape, ListItem, ListItemSpacing, PopoverMenu,
- PopoverMenuHandle, Tooltip,
-};
+use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, PopoverTrigger};
use workspace::ShowConfiguration;
action_with_deprecated_aliases!(
@@ -31,7 +28,6 @@ pub struct LanguageModelSelector {
/// The task used to update the picker's matches when there is a change to
/// the language model registry.
update_matches_task: Option<Task<()>>,
- popover_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
_authenticate_all_providers_task: Task<()>,
_subscriptions: Vec<Subscription>,
}
@@ -63,7 +59,6 @@ impl LanguageModelSelector {
LanguageModelSelector {
picker,
update_matches_task: None,
- popover_menu_handle: PopoverMenuHandle::default(),
_authenticate_all_providers_task: Self::authenticate_all_providers(cx),
_subscriptions: vec![cx.subscribe_in(
&LanguageModelRegistry::global(cx),
@@ -73,15 +68,6 @@ impl LanguageModelSelector {
}
}
- pub fn toggle_model_selector(
- &mut self,
- _: &ToggleModelSelector,
- window: &mut Window,
- cx: &mut Context<Self>,
- ) {
- self.popover_menu_handle.toggle(window, cx);
- }
-
fn handle_language_model_registry_event(
&mut self,
_registry: &Entity<LanguageModelRegistry>,
@@ -201,6 +187,65 @@ impl Render for LanguageModelSelector {
}
}
+#[derive(IntoElement)]
+pub struct LanguageModelSelectorPopoverMenu<T, TT>
+where
+ T: PopoverTrigger + ButtonCommon,
+ TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
+{
+ language_model_selector: Entity<LanguageModelSelector>,
+ trigger: T,
+ tooltip: TT,
+ handle: Option<PopoverMenuHandle<LanguageModelSelector>>,
+ anchor: Corner,
+}
+
+impl<T, TT> LanguageModelSelectorPopoverMenu<T, TT>
+where
+ T: PopoverTrigger + ButtonCommon,
+ TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
+{
+ pub fn new(
+ language_model_selector: Entity<LanguageModelSelector>,
+ trigger: T,
+ tooltip: TT,
+ anchor: Corner,
+ ) -> Self {
+ Self {
+ language_model_selector,
+ trigger,
+ tooltip,
+ handle: None,
+ anchor,
+ }
+ }
+
+ pub fn with_handle(mut self, handle: PopoverMenuHandle<LanguageModelSelector>) -> Self {
+ self.handle = Some(handle);
+ self
+ }
+}
+
+impl<T, TT> RenderOnce for LanguageModelSelectorPopoverMenu<T, TT>
+where
+ T: PopoverTrigger + ButtonCommon,
+ TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
+{
+ fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
+ let language_model_selector = self.language_model_selector.clone();
+
+ PopoverMenu::new("model-switcher")
+ .menu(move |_window, _cx| Some(language_model_selector.clone()))
+ .trigger_with_tooltip(self.trigger, self.tooltip)
+ .anchor(self.anchor)
+ .when_some(self.handle.clone(), |menu, handle| menu.with_handle(handle))
+ .offset(gpui::Point {
+ x: px(0.0),
+ y: px(-2.0),
+ })
+ }
+}
+
#[derive(Clone)]
struct ModelInfo {
model: Arc<dyn LanguageModel>,
@@ -482,114 +527,3 @@ impl PickerDelegate for LanguageModelPickerDelegate {
)
}
}
-
-pub fn inline_language_model_selector(
- on_model_changed: impl Fn(Arc<dyn LanguageModel>, &App) + 'static,
-) -> PopoverMenu<LanguageModelSelector> {
- let on_model_changed = Rc::new(on_model_changed);
- PopoverMenu::new("popover-button")
- .menu(move |window, cx| {
- Some(cx.new(|cx| {
- LanguageModelSelector::new(
- {
- let on_model_changed = on_model_changed.clone();
- move |model, cx| {
- on_model_changed(model, cx);
- }
- },
- window,
- cx,
- )
- }))
- })
- .trigger_with_tooltip(
- IconButton::new("context", IconName::SettingsAlt)
- .shape(IconButtonShape::Square)
- .icon_size(IconSize::Small)
- .icon_color(Color::Muted),
- move |window, cx| {
- Tooltip::with_meta(
- format!(
- "Using {}",
- LanguageModelRegistry::read_global(cx)
- .active_model()
- .map(|model| model.name().0)
- .unwrap_or_else(|| "No model selected".into()),
- ),
- None,
- "Change Model",
- window,
- cx,
- )
- },
- )
- .anchor(gpui::Corner::TopRight)
- .offset(gpui::Point {
- x: px(0.0),
- y: px(-2.0),
- })
-}
-
-pub fn assistant_language_model_selector(
- keybinding_target: FocusHandle,
- menu_handle: Option<PopoverMenuHandle<LanguageModelSelector>>,
- cx: &App,
- on_model_changed: impl Fn(Arc<dyn LanguageModel>, &App) + 'static,
-) -> PopoverMenu<LanguageModelSelector> {
- let active_model = LanguageModelRegistry::read_global(cx).active_model();
- let model_name = match active_model {
- Some(model) => model.name().0,
- _ => SharedString::from("No model selected"),
- };
-
- let on_model_changed = Rc::new(on_model_changed);
-
- PopoverMenu::new("popover-button")
- .menu(move |window, cx| {
- Some(cx.new(|cx| {
- LanguageModelSelector::new(
- {
- let on_model_changed = on_model_changed.clone();
- move |model, cx| {
- on_model_changed(model, cx);
- }
- },
- window,
- cx,
- )
- }))
- })
- .trigger_with_tooltip(
- ButtonLike::new("active-model")
- .style(ButtonStyle::Subtle)
- .child(
- h_flex()
- .gap_0p5()
- .child(
- Label::new(model_name)
- .size(LabelSize::Small)
- .color(Color::Muted),
- )
- .child(
- Icon::new(IconName::ChevronDown)
- .color(Color::Muted)
- .size(IconSize::XSmall),
- ),
- ),
- move |window, cx| {
- Tooltip::for_action_in(
- "Change Model",
- &ToggleModelSelector,
- &keybinding_target,
- window,
- cx,
- )
- },
- )
- .anchor(Corner::BottomRight)
- .when_some(menu_handle, |el, handle| el.with_handle(handle))
- .offset(gpui::Point {
- x: px(0.0),
- y: px(-2.0),
- })
-}