agent2: Add new "new thread" selector in the toolbar (#36133)

Release Notes:

- N/A
This commit is contained in:
Danilo Leal 2025-08-13 14:45:37 -03:00 committed by GitHub
parent 9a375f1419
commit cb0bc463f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 526 additions and 328 deletions

View file

@ -239,6 +239,7 @@
"ctrl-shift-a": "agent::ToggleContextPicker", "ctrl-shift-a": "agent::ToggleContextPicker",
"ctrl-shift-j": "agent::ToggleNavigationMenu", "ctrl-shift-j": "agent::ToggleNavigationMenu",
"ctrl-shift-i": "agent::ToggleOptionsMenu", "ctrl-shift-i": "agent::ToggleOptionsMenu",
"ctrl-alt-shift-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor", "shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl->": "assistant::QuoteSelection", "ctrl->": "assistant::QuoteSelection",
"ctrl-alt-e": "agent::RemoveAllContext", "ctrl-alt-e": "agent::RemoveAllContext",

View file

@ -279,6 +279,7 @@
"cmd-shift-a": "agent::ToggleContextPicker", "cmd-shift-a": "agent::ToggleContextPicker",
"cmd-shift-j": "agent::ToggleNavigationMenu", "cmd-shift-j": "agent::ToggleNavigationMenu",
"cmd-shift-i": "agent::ToggleOptionsMenu", "cmd-shift-i": "agent::ToggleOptionsMenu",
"cmd-alt-shift-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor", "shift-alt-escape": "agent::ExpandMessageEditor",
"cmd->": "assistant::QuoteSelection", "cmd->": "assistant::QuoteSelection",
"cmd-alt-e": "agent::RemoveAllContext", "cmd-alt-e": "agent::RemoveAllContext",

View file

@ -12,12 +12,12 @@ use serde::{Deserialize, Serialize};
use crate::NewExternalAgentThread; use crate::NewExternalAgentThread;
use crate::agent_diff::AgentDiffThread; use crate::agent_diff::AgentDiffThread;
use crate::message_editor::{MAX_EDITOR_LINES, MIN_EDITOR_LINES}; use crate::message_editor::{MAX_EDITOR_LINES, MIN_EDITOR_LINES};
use crate::ui::NewThreadButton;
use crate::{ use crate::{
AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode, AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode,
DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread, DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell,
ResetTrialUpsell, ToggleBurnMode, ToggleContextPicker, ToggleNavigationMenu, ToggleOptionsMenu, ResetTrialUpsell, ToggleBurnMode, ToggleContextPicker, ToggleNavigationMenu,
ToggleNewThreadMenu, ToggleOptionsMenu,
acp::AcpThreadView, acp::AcpThreadView,
active_thread::{self, ActiveThread, ActiveThreadEvent}, active_thread::{self, ActiveThread, ActiveThreadEvent},
agent_configuration::{AgentConfiguration, AssistantConfigurationEvent}, agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
@ -67,8 +67,8 @@ use theme::ThemeSettings;
use time::UtcOffset; use time::UtcOffset;
use ui::utils::WithRemSize; use ui::utils::WithRemSize;
use ui::{ use ui::{
Banner, Callout, ContextMenu, ContextMenuEntry, ElevationIndex, KeyBinding, PopoverMenu, Banner, ButtonLike, Callout, ContextMenu, ContextMenuEntry, ElevationIndex, KeyBinding,
PopoverMenuHandle, ProgressBar, Tab, Tooltip, prelude::*, PopoverMenu, PopoverMenuHandle, ProgressBar, Tab, Tooltip, prelude::*,
}; };
use util::ResultExt as _; use util::ResultExt as _;
use workspace::{ use workspace::{
@ -86,6 +86,7 @@ const AGENT_PANEL_KEY: &str = "agent_panel";
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct SerializedAgentPanel { struct SerializedAgentPanel {
width: Option<Pixels>, width: Option<Pixels>,
selected_agent: Option<AgentType>,
} }
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
@ -179,6 +180,14 @@ pub fn init(cx: &mut App) {
}); });
} }
}) })
.register_action(|workspace, _: &ToggleNewThreadMenu, window, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
workspace.focus_panel::<AgentPanel>(window, cx);
panel.update(cx, |panel, cx| {
panel.toggle_new_thread_menu(&ToggleNewThreadMenu, window, cx);
});
}
})
.register_action(|workspace, _: &OpenOnboardingModal, window, cx| { .register_action(|workspace, _: &OpenOnboardingModal, window, cx| {
AgentOnboardingModal::toggle(workspace, window, cx) AgentOnboardingModal::toggle(workspace, window, cx)
}) })
@ -223,6 +232,36 @@ enum WhichFontSize {
None, None,
} }
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AgentType {
#[default]
Zed,
TextThread,
Gemini,
ClaudeCode,
NativeAgent,
}
impl AgentType {
fn label(self) -> impl Into<SharedString> {
match self {
Self::Zed | Self::TextThread => "Zed",
Self::NativeAgent => "Agent 2",
Self::Gemini => "Gemini",
Self::ClaudeCode => "Claude Code",
}
}
fn icon(self) -> IconName {
match self {
Self::Zed | Self::TextThread => IconName::AiZed,
Self::NativeAgent => IconName::ZedAssistant,
Self::Gemini => IconName::AiGemini,
Self::ClaudeCode => IconName::AiClaude,
}
}
}
impl ActiveView { impl ActiveView {
pub fn which_font_size_used(&self) -> WhichFontSize { pub fn which_font_size_used(&self) -> WhichFontSize {
match self { match self {
@ -453,16 +492,21 @@ pub struct AgentPanel {
zoomed: bool, zoomed: bool,
pending_serialization: Option<Task<Result<()>>>, pending_serialization: Option<Task<Result<()>>>,
onboarding: Entity<AgentPanelOnboarding>, onboarding: Entity<AgentPanelOnboarding>,
selected_agent: AgentType,
} }
impl AgentPanel { impl AgentPanel {
fn serialize(&mut self, cx: &mut Context<Self>) { fn serialize(&mut self, cx: &mut Context<Self>) {
let width = self.width; let width = self.width;
let selected_agent = self.selected_agent;
self.pending_serialization = Some(cx.background_spawn(async move { self.pending_serialization = Some(cx.background_spawn(async move {
KEY_VALUE_STORE KEY_VALUE_STORE
.write_kvp( .write_kvp(
AGENT_PANEL_KEY.into(), AGENT_PANEL_KEY.into(),
serde_json::to_string(&SerializedAgentPanel { width })?, serde_json::to_string(&SerializedAgentPanel {
width,
selected_agent: Some(selected_agent),
})?,
) )
.await?; .await?;
anyhow::Ok(()) anyhow::Ok(())
@ -531,6 +575,9 @@ impl AgentPanel {
if let Some(serialized_panel) = serialized_panel { if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| { panel.update(cx, |panel, cx| {
panel.width = serialized_panel.width.map(|w| w.round()); panel.width = serialized_panel.width.map(|w| w.round());
if let Some(selected_agent) = serialized_panel.selected_agent {
panel.selected_agent = selected_agent;
}
cx.notify(); cx.notify();
}); });
} }
@ -732,6 +779,7 @@ impl AgentPanel {
zoomed: false, zoomed: false,
pending_serialization: None, pending_serialization: None,
onboarding, onboarding,
selected_agent: AgentType::default(),
} }
} }
@ -1174,6 +1222,15 @@ impl AgentPanel {
self.agent_panel_menu_handle.toggle(window, cx); self.agent_panel_menu_handle.toggle(window, cx);
} }
pub fn toggle_new_thread_menu(
&mut self,
_: &ToggleNewThreadMenu,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.new_thread_menu_handle.toggle(window, cx);
}
pub fn increase_font_size( pub fn increase_font_size(
&mut self, &mut self,
action: &IncreaseBufferFontSize, action: &IncreaseBufferFontSize,
@ -1581,6 +1638,17 @@ impl AgentPanel {
menu menu
} }
pub fn set_selected_agent(&mut self, agent: AgentType, cx: &mut Context<Self>) {
if self.selected_agent != agent {
self.selected_agent = agent;
self.serialize(cx);
}
}
pub fn selected_agent(&self) -> AgentType {
self.selected_agent
}
} }
impl Focusable for AgentPanel { impl Focusable for AgentPanel {
@ -1811,200 +1879,24 @@ impl AgentPanel {
.into_any() .into_any()
} }
fn render_toolbar(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render_panel_options_menu(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let user_store = self.user_store.read(cx); let user_store = self.user_store.read(cx);
let usage = user_store.model_request_usage(); let usage = user_store.model_request_usage();
let account_url = zed_urls::account_url(cx); let account_url = zed_urls::account_url(cx);
let focus_handle = self.focus_handle(cx); let focus_handle = self.focus_handle(cx);
let go_back_button = div().child(
IconButton::new("go-back", IconName::ArrowLeft)
.icon_size(IconSize::Small)
.on_click(cx.listener(|this, _, window, cx| {
this.go_back(&workspace::GoBack, window, cx);
}))
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Go Back",
&workspace::GoBack,
&focus_handle,
window,
cx,
)
}
}),
);
let recent_entries_menu = div().child(
PopoverMenu::new("agent-nav-menu")
.trigger_with_tooltip(
IconButton::new("agent-nav-menu", IconName::MenuAlt)
.icon_size(IconSize::Small)
.style(ui::ButtonStyle::Subtle),
{
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Toggle Panel Menu",
&ToggleNavigationMenu,
&focus_handle,
window,
cx,
)
}
},
)
.anchor(Corner::TopLeft)
.with_handle(self.assistant_navigation_menu_handle.clone())
.menu({
let menu = self.assistant_navigation_menu.clone();
move |window, cx| {
if let Some(menu) = menu.as_ref() {
menu.update(cx, |_, cx| {
cx.defer_in(window, |menu, window, cx| {
menu.rebuild(window, cx);
});
})
}
menu.clone()
}
}),
);
let full_screen_label = if self.is_zoomed(window, cx) { let full_screen_label = if self.is_zoomed(window, cx) {
"Disable Full Screen" "Disable Full Screen"
} else { } else {
"Enable Full Screen" "Enable Full Screen"
}; };
let active_thread = match &self.active_view { PopoverMenu::new("agent-options-menu")
ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
ActiveView::ExternalAgentThread { .. }
| ActiveView::TextThread { .. }
| ActiveView::History
| ActiveView::Configuration => None,
};
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({
let focus_handle = focus_handle.clone();
move |window, cx| {
let active_thread = active_thread.clone();
Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
menu = menu
.context(focus_handle.clone())
.when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
this.header("Zed Agent")
})
.when_some(active_thread, |this, active_thread| {
let thread = active_thread.read(cx);
if !thread.is_empty() {
let thread_id = thread.id().clone();
this.item(
ContextMenuEntry::new("New From Summary")
.icon(IconName::ThreadFromSummary)
.icon_color(Color::Muted)
.handler(move |window, cx| {
window.dispatch_action(
Box::new(NewThread {
from_thread_id: Some(thread_id.clone()),
}),
cx,
);
}),
)
} else {
this
}
})
.item(
ContextMenuEntry::new("New Thread")
.icon(IconName::Thread)
.icon_color(Color::Muted)
.action(NewThread::default().boxed_clone())
.handler(move |window, cx| {
window.dispatch_action(
NewThread::default().boxed_clone(),
cx,
);
}),
)
.item(
ContextMenuEntry::new("New Text Thread")
.icon(IconName::TextThread)
.icon_color(Color::Muted)
.action(NewTextThread.boxed_clone())
.handler(move |window, cx| {
window.dispatch_action(NewTextThread.boxed_clone(), cx);
}),
)
.when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
this.separator()
.header("External Agents")
.item(
ContextMenuEntry::new("New Gemini Thread")
.icon(IconName::AiGemini)
.icon_color(Color::Muted)
.handler(move |window, cx| {
window.dispatch_action(
NewExternalAgentThread {
agent: Some(crate::ExternalAgent::Gemini),
}
.boxed_clone(),
cx,
);
}),
)
.item(
ContextMenuEntry::new("New Claude Code Thread")
.icon(IconName::AiClaude)
.icon_color(Color::Muted)
.handler(move |window, cx| {
window.dispatch_action(
NewExternalAgentThread {
agent: Some(
crate::ExternalAgent::ClaudeCode,
),
}
.boxed_clone(),
cx,
);
}),
)
.item(
ContextMenuEntry::new("New Native Agent Thread")
.icon(IconName::ZedAssistant)
.icon_color(Color::Muted)
.handler(move |window, cx| {
window.dispatch_action(
NewExternalAgentThread {
agent: Some(
crate::ExternalAgent::NativeAgent,
),
}
.boxed_clone(),
cx,
);
}),
)
});
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),
@ -2087,6 +1979,139 @@ impl AgentPanel {
menu menu
})) }))
} }
})
}
fn render_recent_entries_menu(
&self,
icon: IconName,
cx: &mut Context<Self>,
) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
PopoverMenu::new("agent-nav-menu")
.trigger_with_tooltip(
IconButton::new("agent-nav-menu", icon)
.icon_size(IconSize::Small)
.style(ui::ButtonStyle::Subtle),
{
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Toggle Panel Menu",
&ToggleNavigationMenu,
&focus_handle,
window,
cx,
)
}
},
)
.anchor(Corner::TopLeft)
.with_handle(self.assistant_navigation_menu_handle.clone())
.menu({
let menu = self.assistant_navigation_menu.clone();
move |window, cx| {
if let Some(menu) = menu.as_ref() {
menu.update(cx, |_, cx| {
cx.defer_in(window, |menu, window, cx| {
menu.rebuild(window, cx);
});
})
}
menu.clone()
}
})
}
fn render_toolbar_back_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
IconButton::new("go-back", IconName::ArrowLeft)
.icon_size(IconSize::Small)
.on_click(cx.listener(|this, _, window, cx| {
this.go_back(&workspace::GoBack, window, cx);
}))
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, window, cx)
}
})
}
fn render_toolbar_old(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
let active_thread = match &self.active_view {
ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
ActiveView::ExternalAgentThread { .. }
| ActiveView::TextThread { .. }
| ActiveView::History
| ActiveView::Configuration => None,
};
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({
let focus_handle = focus_handle.clone();
move |window, cx| {
let active_thread = active_thread.clone();
Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
menu = menu
.context(focus_handle.clone())
.when_some(active_thread, |this, active_thread| {
let thread = active_thread.read(cx);
if !thread.is_empty() {
let thread_id = thread.id().clone();
this.item(
ContextMenuEntry::new("New From Summary")
.icon(IconName::ThreadFromSummary)
.icon_color(Color::Muted)
.handler(move |window, cx| {
window.dispatch_action(
Box::new(NewThread {
from_thread_id: Some(thread_id.clone()),
}),
cx,
);
}),
)
} else {
this
}
})
.item(
ContextMenuEntry::new("New Thread")
.icon(IconName::Thread)
.icon_color(Color::Muted)
.action(NewThread::default().boxed_clone())
.handler(move |window, cx| {
window.dispatch_action(
NewThread::default().boxed_clone(),
cx,
);
}),
)
.item(
ContextMenuEntry::new("New Text Thread")
.icon(IconName::TextThread)
.icon_color(Color::Muted)
.action(NewTextThread.boxed_clone())
.handler(move |window, cx| {
window.dispatch_action(NewTextThread.boxed_clone(), cx);
}),
);
menu
}))
}
}); });
h_flex() h_flex()
@ -2105,8 +2130,12 @@ impl AgentPanel {
.pl_1() .pl_1()
.gap_1() .gap_1()
.child(match &self.active_view { .child(match &self.active_view {
ActiveView::History | ActiveView::Configuration => go_back_button, ActiveView::History | ActiveView::Configuration => {
_ => recent_entries_menu, self.render_toolbar_back_button(cx).into_any_element()
}
_ => self
.render_recent_entries_menu(IconName::MenuAlt, cx)
.into_any_element(),
}) })
.child(self.render_title_view(window, cx)), .child(self.render_title_view(window, cx)),
) )
@ -2123,11 +2152,308 @@ impl AgentPanel {
.border_l_1() .border_l_1()
.border_color(cx.theme().colors().border) .border_color(cx.theme().colors().border)
.child(new_thread_menu) .child(new_thread_menu)
.child(agent_panel_menu), .child(self.render_panel_options_menu(window, cx)),
), ),
) )
} }
fn render_toolbar_new(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
let active_thread = match &self.active_view {
ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
ActiveView::ExternalAgentThread { .. }
| ActiveView::TextThread { .. }
| ActiveView::History
| ActiveView::Configuration => None,
};
let new_thread_menu = PopoverMenu::new("new_thread_menu")
.trigger_with_tooltip(
ButtonLike::new("new_thread_menu_btn").child(
h_flex()
.group("agent-selector")
.gap_1p5()
.child(
h_flex()
.relative()
.size_4()
.justify_center()
.child(
h_flex()
.group_hover("agent-selector", |s| s.invisible())
.child(
Icon::new(self.selected_agent.icon())
.color(Color::Muted),
),
)
.child(
h_flex()
.absolute()
.invisible()
.group_hover("agent-selector", |s| s.visible())
.child(Icon::new(IconName::Plus)),
),
)
.child(Label::new(self.selected_agent.label())),
),
{
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"New…",
&ToggleNewThreadMenu,
&focus_handle,
window,
cx,
)
}
},
)
.anchor(Corner::TopLeft)
.with_handle(self.new_thread_menu_handle.clone())
.menu({
let focus_handle = focus_handle.clone();
let workspace = self.workspace.clone();
move |window, cx| {
let active_thread = active_thread.clone();
Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
menu = menu
.context(focus_handle.clone())
.header("Zed Agent")
.when_some(active_thread, |this, active_thread| {
let thread = active_thread.read(cx);
if !thread.is_empty() {
let thread_id = thread.id().clone();
this.item(
ContextMenuEntry::new("New From Summary")
.icon(IconName::ThreadFromSummary)
.icon_color(Color::Muted)
.handler(move |window, cx| {
window.dispatch_action(
Box::new(NewThread {
from_thread_id: Some(thread_id.clone()),
}),
cx,
);
}),
)
} else {
this
}
})
.item(
ContextMenuEntry::new("New Thread")
.icon(IconName::Thread)
.icon_color(Color::Muted)
.action(NewThread::default().boxed_clone())
.handler({
let workspace = workspace.clone();
move |window, cx| {
if let Some(workspace) = workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
if let Some(panel) =
workspace.panel::<AgentPanel>(cx)
{
panel.update(cx, |panel, cx| {
panel.set_selected_agent(
AgentType::Zed,
cx,
);
});
}
});
}
window.dispatch_action(
NewThread::default().boxed_clone(),
cx,
);
}
}),
)
.item(
ContextMenuEntry::new("New Text Thread")
.icon(IconName::TextThread)
.icon_color(Color::Muted)
.action(NewTextThread.boxed_clone())
.handler({
let workspace = workspace.clone();
move |window, cx| {
if let Some(workspace) = workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
if let Some(panel) =
workspace.panel::<AgentPanel>(cx)
{
panel.update(cx, |panel, cx| {
panel.set_selected_agent(
AgentType::TextThread,
cx,
);
});
}
});
}
window.dispatch_action(NewTextThread.boxed_clone(), cx);
}
}),
)
.item(
ContextMenuEntry::new("New Native Agent Thread")
.icon(IconName::ZedAssistant)
.icon_color(Color::Muted)
.handler({
let workspace = workspace.clone();
move |window, cx| {
if let Some(workspace) = workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
if let Some(panel) =
workspace.panel::<AgentPanel>(cx)
{
panel.update(cx, |panel, cx| {
panel.set_selected_agent(
AgentType::NativeAgent,
cx,
);
});
}
});
}
window.dispatch_action(
NewExternalAgentThread {
agent: Some(crate::ExternalAgent::NativeAgent),
}
.boxed_clone(),
cx,
);
}
}),
)
.separator()
.header("External Agents")
.item(
ContextMenuEntry::new("New Gemini Thread")
.icon(IconName::AiGemini)
.icon_color(Color::Muted)
.handler({
let workspace = workspace.clone();
move |window, cx| {
if let Some(workspace) = workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
if let Some(panel) =
workspace.panel::<AgentPanel>(cx)
{
panel.update(cx, |panel, cx| {
panel.set_selected_agent(
AgentType::Gemini,
cx,
);
});
}
});
}
window.dispatch_action(
NewExternalAgentThread {
agent: Some(crate::ExternalAgent::Gemini),
}
.boxed_clone(),
cx,
);
}
}),
)
.item(
ContextMenuEntry::new("New Claude Code Thread")
.icon(IconName::AiClaude)
.icon_color(Color::Muted)
.handler({
let workspace = workspace.clone();
move |window, cx| {
if let Some(workspace) = workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
if let Some(panel) =
workspace.panel::<AgentPanel>(cx)
{
panel.update(cx, |panel, cx| {
panel.set_selected_agent(
AgentType::ClaudeCode,
cx,
);
});
}
});
}
window.dispatch_action(
NewExternalAgentThread {
agent: Some(crate::ExternalAgent::ClaudeCode),
}
.boxed_clone(),
cx,
);
}
}),
);
menu
}))
}
});
h_flex()
.id("agent-panel-toolbar")
.h(Tab::container_height(cx))
.max_w_full()
.flex_none()
.justify_between()
.gap_2()
.bg(cx.theme().colors().tab_bar_background)
.border_b_1()
.border_color(cx.theme().colors().border)
.child(
h_flex()
.size_full()
.gap(DynamicSpacing::Base08.rems(cx))
.child(match &self.active_view {
ActiveView::History | ActiveView::Configuration => {
self.render_toolbar_back_button(cx).into_any_element()
}
_ => h_flex()
.h_full()
.px(DynamicSpacing::Base04.rems(cx))
.border_r_1()
.border_color(cx.theme().colors().border)
.child(new_thread_menu)
.into_any_element(),
})
.child(self.render_title_view(window, cx)),
)
.child(
h_flex()
.h_full()
.gap_2()
.children(self.render_token_count(cx))
.child(
h_flex()
.h_full()
.gap(DynamicSpacing::Base02.rems(cx))
.pl(DynamicSpacing::Base04.rems(cx))
.pr(DynamicSpacing::Base06.rems(cx))
.border_l_1()
.border_color(cx.theme().colors().border)
.child(self.render_recent_entries_menu(IconName::HistoryRerun, cx))
.child(self.render_panel_options_menu(window, cx)),
),
)
}
fn render_toolbar(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
if cx.has_flag::<feature_flags::AcpFeatureFlag>() {
self.render_toolbar_new(window, cx).into_any_element()
} else {
self.render_toolbar_old(window, cx).into_any_element()
}
}
fn render_token_count(&self, cx: &App) -> Option<AnyElement> { fn render_token_count(&self, cx: &App) -> Option<AnyElement> {
match &self.active_view { match &self.active_view {
ActiveView::Thread { ActiveView::Thread {
@ -2576,138 +2902,6 @@ impl AgentPanel {
}, },
)), )),
) )
.child(self.render_empty_state_section_header("Start", None, cx))
.child(
v_flex()
.p_1()
.gap_2()
.child(
h_flex()
.w_full()
.gap_2()
.child(
NewThreadButton::new(
"new-thread-btn",
"New Thread",
IconName::Thread,
)
.keybinding(KeyBinding::for_action_in(
&NewThread::default(),
&self.focus_handle(cx),
window,
cx,
))
.on_click(
|window, cx| {
window.dispatch_action(
NewThread::default().boxed_clone(),
cx,
)
},
),
)
.child(
NewThreadButton::new(
"new-text-thread-btn",
"New Text Thread",
IconName::TextThread,
)
.keybinding(KeyBinding::for_action_in(
&NewTextThread,
&self.focus_handle(cx),
window,
cx,
))
.on_click(
|window, cx| {
window.dispatch_action(Box::new(NewTextThread), cx)
},
),
),
)
.when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
this.child(
h_flex()
.w_full()
.gap_2()
.child(
NewThreadButton::new(
"new-gemini-thread-btn",
"New Gemini Thread",
IconName::AiGemini,
)
// .keybinding(KeyBinding::for_action_in(
// &OpenHistory,
// &self.focus_handle(cx),
// window,
// cx,
// ))
.on_click(
|window, cx| {
window.dispatch_action(
Box::new(NewExternalAgentThread {
agent: Some(
crate::ExternalAgent::Gemini,
),
}),
cx,
)
},
),
)
.child(
NewThreadButton::new(
"new-claude-thread-btn",
"New Claude Code Thread",
IconName::AiClaude,
)
// .keybinding(KeyBinding::for_action_in(
// &OpenHistory,
// &self.focus_handle(cx),
// window,
// cx,
// ))
.on_click(
|window, cx| {
window.dispatch_action(
Box::new(NewExternalAgentThread {
agent: Some(
crate::ExternalAgent::ClaudeCode,
),
}),
cx,
)
},
),
)
.child(
NewThreadButton::new(
"new-native-agent-thread-btn",
"New Native Agent Thread",
IconName::ZedAssistant,
)
// .keybinding(KeyBinding::for_action_in(
// &OpenHistory,
// &self.focus_handle(cx),
// window,
// cx,
// ))
.on_click(
|window, cx| {
window.dispatch_action(
Box::new(NewExternalAgentThread {
agent: Some(
crate::ExternalAgent::NativeAgent,
),
}),
cx,
)
},
),
),
)
}),
)
.when_some(configuration_error.as_ref(), |this, err| { .when_some(configuration_error.as_ref(), |this, err| {
this.child(self.render_configuration_error(err, &focus_handle, window, cx)) this.child(self.render_configuration_error(err, &focus_handle, window, cx))
}) })

View file

@ -64,6 +64,8 @@ actions!(
NewTextThread, NewTextThread,
/// 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 menu to create new agent threads.
ToggleNewThreadMenu,
/// Toggles the navigation menu for switching between threads and views. /// Toggles the navigation menu for switching between threads and views.
ToggleNavigationMenu, ToggleNavigationMenu,
/// Toggles the options menu for agent settings and preferences. /// Toggles the options menu for agent settings and preferences.

View file

@ -2,7 +2,7 @@ mod agent_notification;
mod burn_mode_tooltip; mod burn_mode_tooltip;
mod context_pill; mod context_pill;
mod end_trial_upsell; mod end_trial_upsell;
mod new_thread_button; // mod new_thread_button;
mod onboarding_modal; mod onboarding_modal;
pub mod preview; pub mod preview;
@ -10,5 +10,5 @@ pub use agent_notification::*;
pub use burn_mode_tooltip::*; pub use burn_mode_tooltip::*;
pub use context_pill::*; pub use context_pill::*;
pub use end_trial_upsell::*; pub use end_trial_upsell::*;
pub use new_thread_button::*; // pub use new_thread_button::*;
pub use onboarding_modal::*; pub use onboarding_modal::*;

View file

@ -11,7 +11,7 @@ pub struct NewThreadButton {
} }
impl NewThreadButton { impl NewThreadButton {
pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>, icon: IconName) -> Self { fn new(id: impl Into<ElementId>, label: impl Into<SharedString>, icon: IconName) -> Self {
Self { Self {
id: id.into(), id: id.into(),
label: label.into(), label: label.into(),
@ -21,12 +21,12 @@ impl NewThreadButton {
} }
} }
pub fn keybinding(mut self, keybinding: Option<ui::KeyBinding>) -> Self { fn keybinding(mut self, keybinding: Option<ui::KeyBinding>) -> Self {
self.keybinding = keybinding; self.keybinding = keybinding;
self self
} }
pub fn on_click<F>(mut self, handler: F) -> Self fn on_click<F>(mut self, handler: F) -> Self
where where
F: Fn(&mut Window, &mut App) + 'static, F: Fn(&mut Window, &mut App) + 'static,
{ {

View file

@ -761,7 +761,7 @@ impl Render for ComponentPreview {
) )
.track_scroll(self.nav_scroll_handle.clone()) .track_scroll(self.nav_scroll_handle.clone())
.p_2p5() .p_2p5()
.w(px(229.)) .w(px(231.)) // Matches perfectly with the size of the "Component Preview" tab, if that's the first one in the pane
.h_full() .h_full()
.flex_1(), .flex_1(),
) )