agent: Add menu in the plus icon button for creating a new thread (#34143)

Release Notes:

- N/A

---------

Co-authored-by: Agus Zubiaga <agus@zed.dev>
This commit is contained in:
Danilo Leal 2025-07-09 15:44:45 -03:00 committed by GitHub
parent 974bc4096a
commit 80eed63255
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 85 additions and 68 deletions

View file

@ -268,6 +268,14 @@
"ctrl-alt-t": "agent::NewThread" "ctrl-alt-t": "agent::NewThread"
} }
}, },
{
"context": "AgentPanel && acp_thread",
"use_key_equivalents": true,
"bindings": {
"ctrl-n": "agent::NewAcpThread",
"ctrl-alt-t": "agent::NewThread"
}
},
{ {
"context": "MessageEditor > Editor", "context": "MessageEditor > Editor",
"bindings": { "bindings": {

View file

@ -309,6 +309,14 @@
"cmd-alt-t": "agent::NewThread" "cmd-alt-t": "agent::NewThread"
} }
}, },
{
"context": "AgentPanel && acp_thread",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "agent::NewAcpThread",
"cmd-alt-t": "agent::NewThread"
}
},
{ {
"context": "MessageEditor > Editor", "context": "MessageEditor > Editor",
"use_key_equivalents": true, "use_key_equivalents": true,

View file

@ -7,7 +7,7 @@ use std::time::Duration;
use db::kvp::{Dismissable, KEY_VALUE_STORE}; use db::kvp::{Dismissable, KEY_VALUE_STORE};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::NewGeminiThread; use crate::NewAcpThread;
use crate::language_model_selector::ToggleModelSelector; use crate::language_model_selector::ToggleModelSelector;
use crate::{ use crate::{
AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode, AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode,
@ -112,7 +112,7 @@ pub fn init(cx: &mut App) {
panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx)); panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));
} }
}) })
.register_action(|workspace, _: &NewGeminiThread, window, cx| { .register_action(|workspace, _: &NewAcpThread, window, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) { if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
workspace.focus_panel::<AgentPanel>(window, cx); workspace.focus_panel::<AgentPanel>(window, cx);
panel.update(cx, |panel, cx| panel.new_gemini_thread(window, cx)); panel.update(cx, |panel, cx| panel.new_gemini_thread(window, cx));
@ -436,7 +436,8 @@ pub struct AgentPanel {
history_store: Entity<HistoryStore>, history_store: Entity<HistoryStore>,
history: Entity<ThreadHistory>, history: Entity<ThreadHistory>,
hovered_recent_history_item: Option<usize>, hovered_recent_history_item: Option<usize>,
assistant_dropdown_menu_handle: PopoverMenuHandle<ContextMenu>, new_thread_menu_handle: PopoverMenuHandle<ContextMenu>,
agent_panel_menu_handle: PopoverMenuHandle<ContextMenu>,
assistant_navigation_menu_handle: PopoverMenuHandle<ContextMenu>, assistant_navigation_menu_handle: PopoverMenuHandle<ContextMenu>,
assistant_navigation_menu: Option<Entity<ContextMenu>>, assistant_navigation_menu: Option<Entity<ContextMenu>>,
width: Option<Pixels>, width: Option<Pixels>,
@ -700,7 +701,8 @@ impl AgentPanel {
history_store: history_store.clone(), history_store: history_store.clone(),
history: cx.new(|cx| ThreadHistory::new(weak_self, history_store, window, cx)), history: cx.new(|cx| ThreadHistory::new(weak_self, history_store, window, cx)),
hovered_recent_history_item: None, hovered_recent_history_item: None,
assistant_dropdown_menu_handle: PopoverMenuHandle::default(), new_thread_menu_handle: PopoverMenuHandle::default(),
agent_panel_menu_handle: PopoverMenuHandle::default(),
assistant_navigation_menu_handle: PopoverMenuHandle::default(), assistant_navigation_menu_handle: PopoverMenuHandle::default(),
assistant_navigation_menu: None, assistant_navigation_menu: None,
width: None, width: None,
@ -1094,7 +1096,7 @@ impl AgentPanel {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.assistant_dropdown_menu_handle.toggle(window, cx); self.agent_panel_menu_handle.toggle(window, cx);
} }
pub fn increase_font_size( pub fn increase_font_size(
@ -1790,7 +1792,45 @@ impl AgentPanel {
| ActiveView::Configuration => None, | ActiveView::Configuration => None,
}; };
let agent_extra_menu = PopoverMenu::new("agent-options-menu") let new_thread_menu = PopoverMenu::new("new_thread_menu")
.trigger_with_tooltip(
IconButton::new("new_thread_menu_btn", IconName::Plus).icon_size(IconSize::Small),
Tooltip::text("New Thread…"),
)
.anchor(Corner::TopRight)
.with_handle(self.new_thread_menu_handle.clone())
.menu(move |window, cx| {
let active_thread = active_thread.clone();
Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
menu = menu
.when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
this.header("Zed Agent")
})
.action("New Thread", NewThread::default().boxed_clone())
.action("New Text Thread", NewTextThread.boxed_clone())
.when_some(active_thread, |this, active_thread| {
let thread = active_thread.read(cx);
if !thread.is_empty() {
this.action(
"New From Summary",
Box::new(NewThread {
from_thread_id: Some(thread.id().clone()),
}),
)
} else {
this
}
})
.when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
this.separator()
.header("External Agents")
.action("New Gemini Thread", NewAcpThread.boxed_clone())
});
menu
}))
});
let agent_panel_menu = PopoverMenu::new("agent-options-menu")
.trigger_with_tooltip( .trigger_with_tooltip(
IconButton::new("agent-options-menu", IconName::Ellipsis) IconButton::new("agent-options-menu", IconName::Ellipsis)
.icon_size(IconSize::Small), .icon_size(IconSize::Small),
@ -1808,44 +1848,9 @@ impl AgentPanel {
}, },
) )
.anchor(Corner::TopRight) .anchor(Corner::TopRight)
.with_handle(self.assistant_dropdown_menu_handle.clone()) .with_handle(self.agent_panel_menu_handle.clone())
.menu(move |window, cx| { .menu(move |window, cx| {
let active_thread = active_thread.clone(); Some(ContextMenu::build(window, cx, |mut menu, _window, _| {
Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
menu = menu
.action("New Thread", NewThread::default().boxed_clone())
.action("New Text Thread", NewTextThread.boxed_clone())
.when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
this.action("New Gemini Thread", NewGeminiThread.boxed_clone())
})
.when_some(active_thread, |this, active_thread| {
let thread = active_thread.read(cx);
if !thread.is_empty() {
this.action(
"New From Summary",
Box::new(NewThread {
from_thread_id: Some(thread.id().clone()),
}),
)
} else {
this
}
})
.separator();
menu = menu
.header("MCP Servers")
.action(
"View Server Extensions",
Box::new(zed_actions::Extensions {
category_filter: Some(
zed_actions::ExtensionCategoryFilter::ContextServers,
),
}),
)
.action("Add Custom Server…", Box::new(AddContextServer))
.separator();
if let Some(usage) = usage { if let Some(usage) = usage {
menu = menu menu = menu
.header_with_link("Prompt Usage", "Manage", account_url.clone()) .header_with_link("Prompt Usage", "Manage", account_url.clone())
@ -1883,6 +1888,19 @@ impl AgentPanel {
.separator() .separator()
} }
menu = menu
.header("MCP Servers")
.action(
"View Server Extensions",
Box::new(zed_actions::Extensions {
category_filter: Some(
zed_actions::ExtensionCategoryFilter::ContextServers,
),
}),
)
.action("Add Custom Server…", Box::new(AddContextServer))
.separator();
menu = menu menu = menu
.action("Rules…", Box::new(OpenRulesLibrary::default())) .action("Rules…", Box::new(OpenRulesLibrary::default()))
.action("Settings", Box::new(OpenConfiguration)) .action("Settings", Box::new(OpenConfiguration))
@ -1924,27 +1942,8 @@ impl AgentPanel {
.px(DynamicSpacing::Base08.rems(cx)) .px(DynamicSpacing::Base08.rems(cx))
.border_l_1() .border_l_1()
.border_color(cx.theme().colors().border) .border_color(cx.theme().colors().border)
.child( .child(new_thread_menu)
IconButton::new("new", IconName::Plus) .child(agent_panel_menu),
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.tooltip(move |window, cx| {
Tooltip::for_action_in(
"New Thread",
&NewThread::default(),
&focus_handle,
window,
cx,
)
})
.on_click(move |_event, window, cx| {
window.dispatch_action(
NewThread::default().boxed_clone(),
cx,
);
}),
)
.child(agent_extra_menu),
), ),
) )
} }
@ -3054,8 +3053,10 @@ impl AgentPanel {
fn key_context(&self) -> KeyContext { fn key_context(&self) -> KeyContext {
let mut key_context = KeyContext::new_with_defaults(); let mut key_context = KeyContext::new_with_defaults();
key_context.add("AgentPanel"); key_context.add("AgentPanel");
if matches!(self.active_view, ActiveView::TextThread { .. }) { match &self.active_view {
key_context.add("prompt_editor"); ActiveView::AcpThread { .. } => key_context.add("acp_thread"),
ActiveView::TextThread { .. } => key_context.add("prompt_editor"),
ActiveView::Thread { .. } | ActiveView::History | ActiveView::Configuration => {}
} }
key_context key_context
} }

View file

@ -57,8 +57,8 @@ actions!(
[ [
/// Creates a new text-based conversation thread. /// Creates a new text-based conversation thread.
NewTextThread, NewTextThread,
/// Creates a new Gemini CLI-based conversation thread. /// Creates a new external agent conversation thread.
NewGeminiThread, NewAcpThread,
/// Toggles the context picker interface for adding files, symbols, or other context. /// Toggles the context picker interface for adding files, symbols, or other context.
ToggleContextPicker, ToggleContextPicker,
/// Toggles the navigation menu for switching between threads and views. /// Toggles the navigation menu for switching between threads and views.