agent2: Add new "new thread" selector in the toolbar (#36133)
Release Notes: - N/A
This commit is contained in:
parent
9a375f1419
commit
cb0bc463f1
7 changed files with 526 additions and 328 deletions
|
@ -239,6 +239,7 @@
|
|||
"ctrl-shift-a": "agent::ToggleContextPicker",
|
||||
"ctrl-shift-j": "agent::ToggleNavigationMenu",
|
||||
"ctrl-shift-i": "agent::ToggleOptionsMenu",
|
||||
"ctrl-alt-shift-n": "agent::ToggleNewThreadMenu",
|
||||
"shift-alt-escape": "agent::ExpandMessageEditor",
|
||||
"ctrl->": "assistant::QuoteSelection",
|
||||
"ctrl-alt-e": "agent::RemoveAllContext",
|
||||
|
|
|
@ -279,6 +279,7 @@
|
|||
"cmd-shift-a": "agent::ToggleContextPicker",
|
||||
"cmd-shift-j": "agent::ToggleNavigationMenu",
|
||||
"cmd-shift-i": "agent::ToggleOptionsMenu",
|
||||
"cmd-alt-shift-n": "agent::ToggleNewThreadMenu",
|
||||
"shift-alt-escape": "agent::ExpandMessageEditor",
|
||||
"cmd->": "assistant::QuoteSelection",
|
||||
"cmd-alt-e": "agent::RemoveAllContext",
|
||||
|
|
|
@ -12,12 +12,12 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::NewExternalAgentThread;
|
||||
use crate::agent_diff::AgentDiffThread;
|
||||
use crate::message_editor::{MAX_EDITOR_LINES, MIN_EDITOR_LINES};
|
||||
use crate::ui::NewThreadButton;
|
||||
use crate::{
|
||||
AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode,
|
||||
DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
|
||||
NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell,
|
||||
ResetTrialUpsell, ToggleBurnMode, ToggleContextPicker, ToggleNavigationMenu, ToggleOptionsMenu,
|
||||
ResetTrialUpsell, ToggleBurnMode, ToggleContextPicker, ToggleNavigationMenu,
|
||||
ToggleNewThreadMenu, ToggleOptionsMenu,
|
||||
acp::AcpThreadView,
|
||||
active_thread::{self, ActiveThread, ActiveThreadEvent},
|
||||
agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
|
||||
|
@ -67,8 +67,8 @@ use theme::ThemeSettings;
|
|||
use time::UtcOffset;
|
||||
use ui::utils::WithRemSize;
|
||||
use ui::{
|
||||
Banner, Callout, ContextMenu, ContextMenuEntry, ElevationIndex, KeyBinding, PopoverMenu,
|
||||
PopoverMenuHandle, ProgressBar, Tab, Tooltip, prelude::*,
|
||||
Banner, ButtonLike, Callout, ContextMenu, ContextMenuEntry, ElevationIndex, KeyBinding,
|
||||
PopoverMenu, PopoverMenuHandle, ProgressBar, Tab, Tooltip, prelude::*,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{
|
||||
|
@ -86,6 +86,7 @@ const AGENT_PANEL_KEY: &str = "agent_panel";
|
|||
#[derive(Serialize, Deserialize)]
|
||||
struct SerializedAgentPanel {
|
||||
width: Option<Pixels>,
|
||||
selected_agent: Option<AgentType>,
|
||||
}
|
||||
|
||||
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| {
|
||||
AgentOnboardingModal::toggle(workspace, window, cx)
|
||||
})
|
||||
|
@ -223,6 +232,36 @@ enum WhichFontSize {
|
|||
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 {
|
||||
pub fn which_font_size_used(&self) -> WhichFontSize {
|
||||
match self {
|
||||
|
@ -453,16 +492,21 @@ pub struct AgentPanel {
|
|||
zoomed: bool,
|
||||
pending_serialization: Option<Task<Result<()>>>,
|
||||
onboarding: Entity<AgentPanelOnboarding>,
|
||||
selected_agent: AgentType,
|
||||
}
|
||||
|
||||
impl AgentPanel {
|
||||
fn serialize(&mut self, cx: &mut Context<Self>) {
|
||||
let width = self.width;
|
||||
let selected_agent = self.selected_agent;
|
||||
self.pending_serialization = Some(cx.background_spawn(async move {
|
||||
KEY_VALUE_STORE
|
||||
.write_kvp(
|
||||
AGENT_PANEL_KEY.into(),
|
||||
serde_json::to_string(&SerializedAgentPanel { width })?,
|
||||
serde_json::to_string(&SerializedAgentPanel {
|
||||
width,
|
||||
selected_agent: Some(selected_agent),
|
||||
})?,
|
||||
)
|
||||
.await?;
|
||||
anyhow::Ok(())
|
||||
|
@ -531,6 +575,9 @@ impl AgentPanel {
|
|||
if let Some(serialized_panel) = serialized_panel {
|
||||
panel.update(cx, |panel, cx| {
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
@ -732,6 +779,7 @@ impl AgentPanel {
|
|||
zoomed: false,
|
||||
pending_serialization: None,
|
||||
onboarding,
|
||||
selected_agent: AgentType::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1174,6 +1222,15 @@ impl AgentPanel {
|
|||
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(
|
||||
&mut self,
|
||||
action: &IncreaseBufferFontSize,
|
||||
|
@ -1581,6 +1638,17 @@ impl AgentPanel {
|
|||
|
||||
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 {
|
||||
|
@ -1811,200 +1879,24 @@ impl AgentPanel {
|
|||
.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 usage = user_store.model_request_usage();
|
||||
|
||||
let account_url = zed_urls::account_url(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) {
|
||||
"Disable Full Screen"
|
||||
} else {
|
||||
"Enable Full Screen"
|
||||
};
|
||||
|
||||
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(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")
|
||||
PopoverMenu::new("agent-options-menu")
|
||||
.trigger_with_tooltip(
|
||||
IconButton::new("agent-options-menu", IconName::Ellipsis)
|
||||
.icon_size(IconSize::Small),
|
||||
|
@ -2087,6 +1979,139 @@ impl AgentPanel {
|
|||
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()
|
||||
|
@ -2105,8 +2130,12 @@ impl AgentPanel {
|
|||
.pl_1()
|
||||
.gap_1()
|
||||
.child(match &self.active_view {
|
||||
ActiveView::History | ActiveView::Configuration => go_back_button,
|
||||
_ => recent_entries_menu,
|
||||
ActiveView::History | ActiveView::Configuration => {
|
||||
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)),
|
||||
)
|
||||
|
@ -2123,11 +2152,308 @@ impl AgentPanel {
|
|||
.border_l_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.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> {
|
||||
match &self.active_view {
|
||||
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| {
|
||||
this.child(self.render_configuration_error(err, &focus_handle, window, cx))
|
||||
})
|
||||
|
|
|
@ -64,6 +64,8 @@ actions!(
|
|||
NewTextThread,
|
||||
/// Toggles the context picker interface for adding files, symbols, or other context.
|
||||
ToggleContextPicker,
|
||||
/// Toggles the menu to create new agent threads.
|
||||
ToggleNewThreadMenu,
|
||||
/// Toggles the navigation menu for switching between threads and views.
|
||||
ToggleNavigationMenu,
|
||||
/// Toggles the options menu for agent settings and preferences.
|
||||
|
|
|
@ -2,7 +2,7 @@ mod agent_notification;
|
|||
mod burn_mode_tooltip;
|
||||
mod context_pill;
|
||||
mod end_trial_upsell;
|
||||
mod new_thread_button;
|
||||
// mod new_thread_button;
|
||||
mod onboarding_modal;
|
||||
pub mod preview;
|
||||
|
||||
|
@ -10,5 +10,5 @@ pub use agent_notification::*;
|
|||
pub use burn_mode_tooltip::*;
|
||||
pub use context_pill::*;
|
||||
pub use end_trial_upsell::*;
|
||||
pub use new_thread_button::*;
|
||||
// pub use new_thread_button::*;
|
||||
pub use onboarding_modal::*;
|
||||
|
|
|
@ -11,7 +11,7 @@ pub struct 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 {
|
||||
id: id.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
|
||||
}
|
||||
|
||||
pub fn on_click<F>(mut self, handler: F) -> Self
|
||||
fn on_click<F>(mut self, handler: F) -> Self
|
||||
where
|
||||
F: Fn(&mut Window, &mut App) + 'static,
|
||||
{
|
||||
|
|
|
@ -761,7 +761,7 @@ impl Render for ComponentPreview {
|
|||
)
|
||||
.track_scroll(self.nav_scroll_handle.clone())
|
||||
.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()
|
||||
.flex_1(),
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue