assistant2: Add profile selector (#27520)
This PR replaces the tool selector with a new profile selector. <img width="1394" alt="Screenshot 2025-03-26 at 2 35 42 PM" src="https://github.com/user-attachments/assets/9631c6e9-9c47-411e-b9fc-5d61ed9ca1fe" /> <img width="1394" alt="Screenshot 2025-03-26 at 2 35 50 PM" src="https://github.com/user-attachments/assets/3abe4e08-d044-4d3f-aa95-f472938452a8" /> Release Notes: - N/A
This commit is contained in:
parent
7e4320f587
commit
cdaad2655a
9 changed files with 268 additions and 181 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -491,6 +491,7 @@ dependencies = [
|
||||||
"prompt_store",
|
"prompt_store",
|
||||||
"proto",
|
"proto",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
|
"regex",
|
||||||
"release_channel",
|
"release_channel",
|
||||||
"rope",
|
"rope",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -622,6 +622,7 @@
|
||||||
// The model to use.
|
// The model to use.
|
||||||
"model": "claude-3-5-sonnet-latest"
|
"model": "claude-3-5-sonnet-latest"
|
||||||
},
|
},
|
||||||
|
"default_profile": "code-writer",
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"read-only": {
|
"read-only": {
|
||||||
"name": "Read-only",
|
"name": "Read-only",
|
||||||
|
|
|
@ -62,6 +62,7 @@ prompt_library.workspace = true
|
||||||
prompt_store.workspace = true
|
prompt_store.workspace = true
|
||||||
proto.workspace = true
|
proto.workspace = true
|
||||||
release_channel.workspace = true
|
release_channel.workspace = true
|
||||||
|
regex.workspace = true
|
||||||
rope.workspace = true
|
rope.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
|
|
@ -11,12 +11,12 @@ mod history_store;
|
||||||
mod inline_assistant;
|
mod inline_assistant;
|
||||||
mod inline_prompt_editor;
|
mod inline_prompt_editor;
|
||||||
mod message_editor;
|
mod message_editor;
|
||||||
|
mod profile_selector;
|
||||||
mod terminal_codegen;
|
mod terminal_codegen;
|
||||||
mod terminal_inline_assistant;
|
mod terminal_inline_assistant;
|
||||||
mod thread;
|
mod thread;
|
||||||
mod thread_history;
|
mod thread_history;
|
||||||
mod thread_store;
|
mod thread_store;
|
||||||
mod tool_selector;
|
|
||||||
mod tool_use;
|
mod tool_use;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,9 @@ use crate::assistant_model_selector::AssistantModelSelector;
|
||||||
use crate::context_picker::{ConfirmBehavior, ContextPicker, ContextPickerCompletionProvider};
|
use crate::context_picker::{ConfirmBehavior, ContextPicker, ContextPickerCompletionProvider};
|
||||||
use crate::context_store::{refresh_context_store_text, ContextStore};
|
use crate::context_store::{refresh_context_store_text, ContextStore};
|
||||||
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||||
|
use crate::profile_selector::ProfileSelector;
|
||||||
use crate::thread::{RequestKind, Thread};
|
use crate::thread::{RequestKind, Thread};
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::ThreadStore;
|
||||||
use crate::tool_selector::ToolSelector;
|
|
||||||
use crate::{Chat, ChatMode, RemoveAllContext, ThreadEvent, ToggleContextPicker};
|
use crate::{Chat, ChatMode, RemoveAllContext, ThreadEvent, ToggleContextPicker};
|
||||||
|
|
||||||
pub struct MessageEditor {
|
pub struct MessageEditor {
|
||||||
|
@ -43,7 +43,7 @@ pub struct MessageEditor {
|
||||||
inline_context_picker: Entity<ContextPicker>,
|
inline_context_picker: Entity<ContextPicker>,
|
||||||
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||||
model_selector: Entity<AssistantModelSelector>,
|
model_selector: Entity<AssistantModelSelector>,
|
||||||
tool_selector: Entity<ToolSelector>,
|
profile_selector: Entity<ProfileSelector>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +57,6 @@ impl MessageEditor {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let tools = thread.read(cx).tools().clone();
|
|
||||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||||
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
|
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
|
||||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||||
|
@ -129,14 +128,14 @@ impl MessageEditor {
|
||||||
inline_context_picker_menu_handle,
|
inline_context_picker_menu_handle,
|
||||||
model_selector: cx.new(|cx| {
|
model_selector: cx.new(|cx| {
|
||||||
AssistantModelSelector::new(
|
AssistantModelSelector::new(
|
||||||
fs,
|
fs.clone(),
|
||||||
model_selector_menu_handle,
|
model_selector_menu_handle,
|
||||||
editor.focus_handle(cx),
|
editor.focus_handle(cx),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
tool_selector: cx.new(|cx| ToolSelector::new(tools, cx)),
|
profile_selector: cx.new(|cx| ProfileSelector::new(fs, thread_store, cx)),
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -624,7 +623,7 @@ impl Render for MessageEditor {
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(h_flex().gap_2().child(self.tool_selector.clone()))
|
.child(h_flex().gap_2().child(self.profile_selector.clone()))
|
||||||
.child(
|
.child(
|
||||||
h_flex().gap_1().child(self.model_selector.clone()).child(
|
h_flex().gap_1().child(self.model_selector.clone()).child(
|
||||||
ButtonLike::new("submit-message")
|
ButtonLike::new("submit-message")
|
||||||
|
|
202
crates/assistant2/src/profile_selector.rs
Normal file
202
crates/assistant2/src/profile_selector.rs
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
use std::sync::{Arc, LazyLock};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use assistant_settings::{AgentProfile, AssistantSettings};
|
||||||
|
use editor::scroll::Autoscroll;
|
||||||
|
use editor::Editor;
|
||||||
|
use fs::Fs;
|
||||||
|
use gpui::{prelude::*, AsyncWindowContext, Entity, Subscription, WeakEntity};
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use regex::Regex;
|
||||||
|
use settings::{update_settings_file, Settings as _, SettingsStore};
|
||||||
|
use ui::{prelude::*, ContextMenu, ContextMenuEntry, PopoverMenu, Tooltip};
|
||||||
|
use util::ResultExt as _;
|
||||||
|
use workspace::{create_and_open_local_file, Workspace};
|
||||||
|
|
||||||
|
use crate::ThreadStore;
|
||||||
|
|
||||||
|
pub struct ProfileSelector {
|
||||||
|
profiles: IndexMap<Arc<str>, AgentProfile>,
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
|
_subscriptions: Vec<Subscription>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProfileSelector {
|
||||||
|
pub fn new(
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Self {
|
||||||
|
let settings_subscription = cx.observe_global::<SettingsStore>(move |this, cx| {
|
||||||
|
this.refresh_profiles(cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut this = Self {
|
||||||
|
profiles: IndexMap::default(),
|
||||||
|
fs,
|
||||||
|
thread_store,
|
||||||
|
_subscriptions: vec![settings_subscription],
|
||||||
|
};
|
||||||
|
this.refresh_profiles(cx);
|
||||||
|
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
fn refresh_profiles(&mut self, cx: &mut Context<Self>) {
|
||||||
|
let settings = AssistantSettings::get_global(cx);
|
||||||
|
|
||||||
|
self.profiles = settings.profiles.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_context_menu(
|
||||||
|
&self,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Entity<ContextMenu> {
|
||||||
|
ContextMenu::build(window, cx, |mut menu, _window, cx| {
|
||||||
|
let settings = AssistantSettings::get_global(cx);
|
||||||
|
let icon_position = IconPosition::Start;
|
||||||
|
|
||||||
|
menu = menu.header("Profiles");
|
||||||
|
for (profile_id, profile) in self.profiles.clone() {
|
||||||
|
menu = menu.toggleable_entry(
|
||||||
|
profile.name.clone(),
|
||||||
|
profile_id == settings.default_profile,
|
||||||
|
icon_position,
|
||||||
|
None,
|
||||||
|
{
|
||||||
|
let fs = self.fs.clone();
|
||||||
|
let thread_store = self.thread_store.clone();
|
||||||
|
move |_window, cx| {
|
||||||
|
update_settings_file::<AssistantSettings>(fs.clone(), cx, {
|
||||||
|
let profile_id = profile_id.clone();
|
||||||
|
move |settings, _cx| {
|
||||||
|
settings.set_profile(profile_id.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
thread_store
|
||||||
|
.update(cx, |this, cx| {
|
||||||
|
this.load_default_profile(cx);
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu = menu.separator();
|
||||||
|
menu = menu.item(
|
||||||
|
ContextMenuEntry::new("Configure Profiles")
|
||||||
|
.icon(IconName::Pencil)
|
||||||
|
.icon_color(Color::Muted)
|
||||||
|
.handler(move |window, cx| {
|
||||||
|
if let Some(workspace) = window.root().flatten() {
|
||||||
|
let workspace = workspace.downgrade();
|
||||||
|
window
|
||||||
|
.spawn(cx, async |cx| {
|
||||||
|
Self::open_profiles_setting_in_editor(workspace, cx).await
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
menu
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn open_profiles_setting_in_editor(
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
cx: &mut AsyncWindowContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
let settings_editor = workspace
|
||||||
|
.update_in(cx, |_, window, cx| {
|
||||||
|
create_and_open_local_file(paths::settings_file(), window, cx, || {
|
||||||
|
settings::initial_user_settings_content().as_ref().into()
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
.await?
|
||||||
|
.downcast::<Editor>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
settings_editor
|
||||||
|
.downgrade()
|
||||||
|
.update_in(cx, |editor, window, cx| {
|
||||||
|
let text = editor.buffer().read(cx).snapshot(cx).text();
|
||||||
|
|
||||||
|
let settings = cx.global::<SettingsStore>();
|
||||||
|
|
||||||
|
let edits =
|
||||||
|
settings.edits_for_update::<AssistantSettings>(
|
||||||
|
&text,
|
||||||
|
|settings| match settings {
|
||||||
|
assistant_settings::AssistantSettingsContent::Versioned(settings) => {
|
||||||
|
match settings {
|
||||||
|
assistant_settings::VersionedAssistantSettingsContent::V2(
|
||||||
|
settings,
|
||||||
|
) => {
|
||||||
|
settings.profiles.get_or_insert_with(IndexMap::default);
|
||||||
|
}
|
||||||
|
assistant_settings::VersionedAssistantSettingsContent::V1(
|
||||||
|
_,
|
||||||
|
) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assistant_settings::AssistantSettingsContent::Legacy(_) => {}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if !edits.is_empty() {
|
||||||
|
editor.edit(edits.iter().cloned(), cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = editor.buffer().read(cx).snapshot(cx).text();
|
||||||
|
|
||||||
|
static PROFILES_REGEX: LazyLock<Regex> =
|
||||||
|
LazyLock::new(|| Regex::new(r#"(?P<key>"profiles":)\s*\{"#).unwrap());
|
||||||
|
let range = PROFILES_REGEX.captures(&text).and_then(|captures| {
|
||||||
|
captures
|
||||||
|
.name("key")
|
||||||
|
.map(|inner_match| inner_match.start()..inner_match.end())
|
||||||
|
});
|
||||||
|
if let Some(range) = range {
|
||||||
|
editor.change_selections(
|
||||||
|
Some(Autoscroll::newest()),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
|selections| {
|
||||||
|
selections.select_ranges(vec![range]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for ProfileSelector {
|
||||||
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let settings = AssistantSettings::get_global(cx);
|
||||||
|
let profile = settings
|
||||||
|
.profiles
|
||||||
|
.get(&settings.default_profile)
|
||||||
|
.map(|profile| profile.name.clone())
|
||||||
|
.unwrap_or_else(|| "Unknown".into());
|
||||||
|
|
||||||
|
let this = cx.entity().clone();
|
||||||
|
PopoverMenu::new("tool-selector")
|
||||||
|
.menu(move |window, cx| {
|
||||||
|
Some(this.update(cx, |this, cx| this.build_context_menu(window, cx)))
|
||||||
|
})
|
||||||
|
.trigger_with_tooltip(
|
||||||
|
Button::new("profile-selector-button", profile)
|
||||||
|
.style(ButtonStyle::Filled)
|
||||||
|
.label_size(LabelSize::Small),
|
||||||
|
Tooltip::text("Change Profile"),
|
||||||
|
)
|
||||||
|
.anchor(gpui::Corner::BottomLeft)
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,8 @@ use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_tool::{ToolId, ToolWorkingSet};
|
use assistant_settings::AssistantSettings;
|
||||||
|
use assistant_tool::{ToolId, ToolSource, ToolWorkingSet};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use context_server::manager::ContextServerManager;
|
use context_server::manager::ContextServerManager;
|
||||||
|
@ -19,6 +20,7 @@ use language_model::{LanguageModelToolUseId, Role};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use prompt_store::PromptBuilder;
|
use prompt_store::PromptBuilder;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use settings::Settings as _;
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
|
|
||||||
use crate::thread::{MessageId, ProjectSnapshot, Thread, ThreadEvent, ThreadId};
|
use crate::thread::{MessageId, ProjectSnapshot, Thread, ThreadEvent, ThreadId};
|
||||||
|
@ -57,6 +59,7 @@ impl ThreadStore {
|
||||||
context_server_tool_ids: HashMap::default(),
|
context_server_tool_ids: HashMap::default(),
|
||||||
threads: Vec::new(),
|
threads: Vec::new(),
|
||||||
};
|
};
|
||||||
|
this.load_default_profile(cx);
|
||||||
this.register_context_server_handlers(cx);
|
this.register_context_server_handlers(cx);
|
||||||
this.reload(cx).detach_and_log_err(cx);
|
this.reload(cx).detach_and_log_err(cx);
|
||||||
|
|
||||||
|
@ -184,6 +187,38 @@ impl ThreadStore {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn load_default_profile(&self, cx: &mut Context<Self>) {
|
||||||
|
let assistant_settings = AssistantSettings::get_global(cx);
|
||||||
|
|
||||||
|
if let Some(profile) = assistant_settings
|
||||||
|
.profiles
|
||||||
|
.get(&assistant_settings.default_profile)
|
||||||
|
{
|
||||||
|
self.tools.disable_source(ToolSource::Native, cx);
|
||||||
|
self.tools.enable(
|
||||||
|
ToolSource::Native,
|
||||||
|
&profile
|
||||||
|
.tools
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(tool, enabled)| enabled.then(|| tool.clone()))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (context_server_id, preset) in &profile.context_servers {
|
||||||
|
self.tools.enable(
|
||||||
|
ToolSource::ContextServer {
|
||||||
|
id: context_server_id.clone().into(),
|
||||||
|
},
|
||||||
|
&preset
|
||||||
|
.tools
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(tool, enabled)| enabled.then(|| tool.clone()))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn register_context_server_handlers(&self, cx: &mut Context<Self>) {
|
fn register_context_server_handlers(&self, cx: &mut Context<Self>) {
|
||||||
cx.subscribe(
|
cx.subscribe(
|
||||||
&self.context_server_manager.clone(),
|
&self.context_server_manager.clone(),
|
||||||
|
|
|
@ -1,172 +0,0 @@
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use assistant_settings::{AgentProfile, AssistantSettings};
|
|
||||||
use assistant_tool::{ToolSource, ToolWorkingSet};
|
|
||||||
use gpui::{Entity, Subscription};
|
|
||||||
use indexmap::IndexMap;
|
|
||||||
use settings::{Settings as _, SettingsStore};
|
|
||||||
use ui::{prelude::*, ContextMenu, PopoverMenu, Tooltip};
|
|
||||||
|
|
||||||
pub struct ToolSelector {
|
|
||||||
profiles: IndexMap<Arc<str>, AgentProfile>,
|
|
||||||
tools: Arc<ToolWorkingSet>,
|
|
||||||
_subscriptions: Vec<Subscription>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToolSelector {
|
|
||||||
pub fn new(tools: Arc<ToolWorkingSet>, cx: &mut Context<Self>) -> Self {
|
|
||||||
let settings_subscription = cx.observe_global::<SettingsStore>(move |this, cx| {
|
|
||||||
this.refresh_profiles(cx);
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut this = Self {
|
|
||||||
profiles: IndexMap::default(),
|
|
||||||
tools,
|
|
||||||
_subscriptions: vec![settings_subscription],
|
|
||||||
};
|
|
||||||
this.refresh_profiles(cx);
|
|
||||||
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
fn refresh_profiles(&mut self, cx: &mut Context<Self>) {
|
|
||||||
let settings = AssistantSettings::get_global(cx);
|
|
||||||
|
|
||||||
self.profiles = settings.profiles.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_context_menu(
|
|
||||||
&self,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Entity<ContextMenu> {
|
|
||||||
let profiles = self.profiles.clone();
|
|
||||||
let tool_set = self.tools.clone();
|
|
||||||
ContextMenu::build_persistent(window, cx, move |mut menu, _window, cx| {
|
|
||||||
let icon_position = IconPosition::End;
|
|
||||||
|
|
||||||
menu = menu.header("Profiles");
|
|
||||||
for (_id, profile) in profiles.clone() {
|
|
||||||
menu = menu.toggleable_entry(profile.name.clone(), false, icon_position, None, {
|
|
||||||
let tools = tool_set.clone();
|
|
||||||
move |_window, cx| {
|
|
||||||
tools.disable_all_tools(cx);
|
|
||||||
|
|
||||||
tools.enable(
|
|
||||||
ToolSource::Native,
|
|
||||||
&profile
|
|
||||||
.tools
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(tool, enabled)| enabled.then(|| tool.clone()))
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (context_server_id, preset) in &profile.context_servers {
|
|
||||||
tools.enable(
|
|
||||||
ToolSource::ContextServer {
|
|
||||||
id: context_server_id.clone().into(),
|
|
||||||
},
|
|
||||||
&preset
|
|
||||||
.tools
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(tool, enabled)| enabled.then(|| tool.clone()))
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
menu = menu.separator();
|
|
||||||
|
|
||||||
let tools_by_source = tool_set.tools_by_source(cx);
|
|
||||||
|
|
||||||
let all_tools_enabled = tool_set.are_all_tools_enabled();
|
|
||||||
menu = menu.toggleable_entry("All Tools", all_tools_enabled, icon_position, None, {
|
|
||||||
let tools = tool_set.clone();
|
|
||||||
move |_window, cx| {
|
|
||||||
if all_tools_enabled {
|
|
||||||
tools.disable_all_tools(cx);
|
|
||||||
} else {
|
|
||||||
tools.enable_all_tools();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (source, tools) in tools_by_source {
|
|
||||||
let mut tools = tools
|
|
||||||
.into_iter()
|
|
||||||
.map(|tool| {
|
|
||||||
let source = tool.source();
|
|
||||||
let name = tool.name().into();
|
|
||||||
let is_enabled = tool_set.is_enabled(&source, &name);
|
|
||||||
|
|
||||||
(source, name, is_enabled)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
if ToolSource::Native == source {
|
|
||||||
tools.sort_by(|(_, name_a, _), (_, name_b, _)| name_a.cmp(name_b));
|
|
||||||
}
|
|
||||||
|
|
||||||
menu = match &source {
|
|
||||||
ToolSource::Native => menu.separator().header("Zed Tools"),
|
|
||||||
ToolSource::ContextServer { id } => {
|
|
||||||
let all_tools_from_source_enabled =
|
|
||||||
tool_set.are_all_tools_from_source_enabled(&source);
|
|
||||||
|
|
||||||
menu.separator().header(id).toggleable_entry(
|
|
||||||
"All Tools",
|
|
||||||
all_tools_from_source_enabled,
|
|
||||||
icon_position,
|
|
||||||
None,
|
|
||||||
{
|
|
||||||
let tools = tool_set.clone();
|
|
||||||
let source = source.clone();
|
|
||||||
move |_window, cx| {
|
|
||||||
if all_tools_from_source_enabled {
|
|
||||||
tools.disable_source(source.clone(), cx);
|
|
||||||
} else {
|
|
||||||
tools.enable_source(&source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (source, name, is_enabled) in tools {
|
|
||||||
menu = menu.toggleable_entry(name.clone(), is_enabled, icon_position, None, {
|
|
||||||
let tools = tool_set.clone();
|
|
||||||
move |_window, _cx| {
|
|
||||||
if is_enabled {
|
|
||||||
tools.disable(source.clone(), &[name.clone()]);
|
|
||||||
} else {
|
|
||||||
tools.enable(source.clone(), &[name.clone()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
menu
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for ToolSelector {
|
|
||||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
|
|
||||||
let this = cx.entity().clone();
|
|
||||||
PopoverMenu::new("tool-selector")
|
|
||||||
.menu(move |window, cx| {
|
|
||||||
Some(this.update(cx, |this, cx| this.build_context_menu(window, cx)))
|
|
||||||
})
|
|
||||||
.trigger_with_tooltip(
|
|
||||||
IconButton::new("tool-selector-button", IconName::SettingsAlt)
|
|
||||||
.icon_size(IconSize::Small)
|
|
||||||
.icon_color(Color::Muted),
|
|
||||||
Tooltip::text("Customize Tools"),
|
|
||||||
)
|
|
||||||
.anchor(gpui::Corner::BottomLeft)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -71,6 +71,7 @@ pub struct AssistantSettings {
|
||||||
pub inline_alternatives: Vec<LanguageModelSelection>,
|
pub inline_alternatives: Vec<LanguageModelSelection>,
|
||||||
pub using_outdated_settings_version: bool,
|
pub using_outdated_settings_version: bool,
|
||||||
pub enable_experimental_live_diffs: bool,
|
pub enable_experimental_live_diffs: bool,
|
||||||
|
pub default_profile: Arc<str>,
|
||||||
pub profiles: IndexMap<Arc<str>, AgentProfile>,
|
pub profiles: IndexMap<Arc<str>, AgentProfile>,
|
||||||
pub always_allow_tool_actions: bool,
|
pub always_allow_tool_actions: bool,
|
||||||
pub notify_when_agent_waiting: bool,
|
pub notify_when_agent_waiting: bool,
|
||||||
|
@ -174,6 +175,7 @@ impl AssistantSettingsContent {
|
||||||
editor_model: None,
|
editor_model: None,
|
||||||
inline_alternatives: None,
|
inline_alternatives: None,
|
||||||
enable_experimental_live_diffs: None,
|
enable_experimental_live_diffs: None,
|
||||||
|
default_profile: None,
|
||||||
profiles: None,
|
profiles: None,
|
||||||
always_allow_tool_actions: None,
|
always_allow_tool_actions: None,
|
||||||
notify_when_agent_waiting: None,
|
notify_when_agent_waiting: None,
|
||||||
|
@ -198,6 +200,7 @@ impl AssistantSettingsContent {
|
||||||
editor_model: None,
|
editor_model: None,
|
||||||
inline_alternatives: None,
|
inline_alternatives: None,
|
||||||
enable_experimental_live_diffs: None,
|
enable_experimental_live_diffs: None,
|
||||||
|
default_profile: None,
|
||||||
profiles: None,
|
profiles: None,
|
||||||
always_allow_tool_actions: None,
|
always_allow_tool_actions: None,
|
||||||
notify_when_agent_waiting: None,
|
notify_when_agent_waiting: None,
|
||||||
|
@ -307,6 +310,18 @@ impl AssistantSettingsContent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_profile(&mut self, profile_id: Arc<str>) {
|
||||||
|
match self {
|
||||||
|
AssistantSettingsContent::Versioned(settings) => match settings {
|
||||||
|
VersionedAssistantSettingsContent::V2(settings) => {
|
||||||
|
settings.default_profile = Some(profile_id);
|
||||||
|
}
|
||||||
|
VersionedAssistantSettingsContent::V1(_) => {}
|
||||||
|
},
|
||||||
|
AssistantSettingsContent::Legacy(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
||||||
|
@ -330,6 +345,7 @@ impl Default for VersionedAssistantSettingsContent {
|
||||||
editor_model: None,
|
editor_model: None,
|
||||||
inline_alternatives: None,
|
inline_alternatives: None,
|
||||||
enable_experimental_live_diffs: None,
|
enable_experimental_live_diffs: None,
|
||||||
|
default_profile: None,
|
||||||
profiles: None,
|
profiles: None,
|
||||||
always_allow_tool_actions: None,
|
always_allow_tool_actions: None,
|
||||||
notify_when_agent_waiting: None,
|
notify_when_agent_waiting: None,
|
||||||
|
@ -370,7 +386,9 @@ pub struct AssistantSettingsContentV2 {
|
||||||
/// Default: false
|
/// Default: false
|
||||||
enable_experimental_live_diffs: Option<bool>,
|
enable_experimental_live_diffs: Option<bool>,
|
||||||
#[schemars(skip)]
|
#[schemars(skip)]
|
||||||
profiles: Option<IndexMap<Arc<str>, AgentProfileContent>>,
|
default_profile: Option<Arc<str>>,
|
||||||
|
#[schemars(skip)]
|
||||||
|
pub profiles: Option<IndexMap<Arc<str>, AgentProfileContent>>,
|
||||||
/// Whenever a tool action would normally wait for your confirmation
|
/// Whenever a tool action would normally wait for your confirmation
|
||||||
/// that you allow it, always choose to allow it.
|
/// that you allow it, always choose to allow it.
|
||||||
///
|
///
|
||||||
|
@ -531,6 +549,7 @@ impl Settings for AssistantSettings {
|
||||||
&mut settings.notify_when_agent_waiting,
|
&mut settings.notify_when_agent_waiting,
|
||||||
value.notify_when_agent_waiting,
|
value.notify_when_agent_waiting,
|
||||||
);
|
);
|
||||||
|
merge(&mut settings.default_profile, value.default_profile);
|
||||||
|
|
||||||
if let Some(profiles) = value.profiles {
|
if let Some(profiles) = value.profiles {
|
||||||
settings
|
settings
|
||||||
|
@ -621,6 +640,7 @@ mod tests {
|
||||||
default_width: None,
|
default_width: None,
|
||||||
default_height: None,
|
default_height: None,
|
||||||
enable_experimental_live_diffs: None,
|
enable_experimental_live_diffs: None,
|
||||||
|
default_profile: None,
|
||||||
profiles: None,
|
profiles: None,
|
||||||
always_allow_tool_actions: None,
|
always_allow_tool_actions: None,
|
||||||
notify_when_agent_waiting: None,
|
notify_when_agent_waiting: None,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue