Show agent icon instead of folder icon in sidebar thread list (#49421)
Replace the generic folder icon next to thread entries in the workspace sidebar with the actual agent icon (Claude, Zed Agent, Gemini, etc.) for the active thread. Release Notes: - N/A Co-authored-by: cameron <cameron.studdstreet@gmail.com> Co-authored-by: Anthony Eid <hello@anthonyeid.me>
This commit is contained in:
1 parent
b88ecd61b2
commit
ed5605bd2c
2 files changed
+68
-40
No files matched your search
@@ -14,7 +14,6 @@ use settings::{LanguageModelProviderSetting, LanguageModelSelection};
|
||||
|
||||
use zed_actions::agent::{OpenClaudeAgentOnboardingModal, ReauthenticateAgent};
|
||||
|
||||
use crate::ManageProfiles;
|
||||
use crate::ui::{AcpOnboardingModal, ClaudeCodeOnboardingModal};
|
||||
use crate::{
|
||||
AddContextServer, AgentDiffPane, CopyThreadToClipboard, Follow, InlineAssistant,
|
||||
@@ -36,6 +35,7 @@ use crate::{
|
||||
ExternalAgent, ExternalAgentInitialContent, NewExternalAgentThread,
|
||||
NewNativeAgentThreadFromSummary,
|
||||
};
|
||||
use crate::{ManageProfiles, acp::thread_view::AcpThreadView};
|
||||
use agent_settings::AgentSettings;
|
||||
use ai_onboarding::AgentPanelOnboarding;
|
||||
use anyhow::{Result, anyhow};
|
||||
@@ -289,7 +289,7 @@ enum HistoryKind {
|
||||
enum ActiveView {
|
||||
Uninitialized,
|
||||
AgentThread {
|
||||
thread_view: Entity<AcpServerView>,
|
||||
server_view: Entity<AcpServerView>,
|
||||
},
|
||||
TextThread {
|
||||
text_thread_editor: Entity<TextThreadEditor>,
|
||||
@@ -855,7 +855,7 @@ impl AgentPanel {
|
||||
|
||||
pub(crate) fn active_thread_view(&self) -> Option<&Entity<AcpServerView>> {
|
||||
match &self.active_view {
|
||||
ActiveView::AgentThread { thread_view, .. } => Some(thread_view),
|
||||
ActiveView::AgentThread { server_view, .. } => Some(server_view),
|
||||
ActiveView::Uninitialized
|
||||
| ActiveView::TextThread { .. }
|
||||
| ActiveView::History { .. }
|
||||
@@ -1529,9 +1529,21 @@ impl AgentPanel {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_active_server_view(&self) -> Option<&Entity<AcpServerView>> {
|
||||
match &self.active_view {
|
||||
ActiveView::AgentThread { server_view } => Some(server_view),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_active_thread_view(&self, cx: &App) -> Option<Entity<AcpThreadView>> {
|
||||
let server_view = self.as_active_server_view()?;
|
||||
server_view.read(cx).active_thread().cloned()
|
||||
}
|
||||
|
||||
pub fn active_agent_thread(&self, cx: &App) -> Option<Entity<AcpThread>> {
|
||||
match &self.active_view {
|
||||
ActiveView::AgentThread { thread_view, .. } => thread_view
|
||||
ActiveView::AgentThread { server_view, .. } => server_view
|
||||
.read(cx)
|
||||
.active_thread()
|
||||
.map(|r| r.read(cx).thread.clone()),
|
||||
@@ -1541,8 +1553,8 @@ impl AgentPanel {
|
||||
|
||||
pub(crate) fn active_native_agent_thread(&self, cx: &App) -> Option<Entity<agent::Thread>> {
|
||||
match &self.active_view {
|
||||
ActiveView::AgentThread { thread_view, .. } => {
|
||||
thread_view.read(cx).as_native_thread(cx)
|
||||
ActiveView::AgentThread { server_view, .. } => {
|
||||
server_view.read(cx).as_native_thread(cx)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
@@ -1592,8 +1604,8 @@ impl AgentPanel {
|
||||
}
|
||||
|
||||
self._active_view_observation = match &self.active_view {
|
||||
ActiveView::AgentThread { thread_view } => {
|
||||
Some(cx.observe(thread_view, |this, _, cx| {
|
||||
ActiveView::AgentThread { server_view } => {
|
||||
Some(cx.observe(server_view, |this, _, cx| {
|
||||
cx.emit(AgentPanelEvent::ActiveViewChanged);
|
||||
this.serialize(cx);
|
||||
cx.notify();
|
||||
@@ -1844,7 +1856,7 @@ impl AgentPanel {
|
||||
.is_some()
|
||||
.then(|| self.thread_store.clone());
|
||||
|
||||
let thread_view = cx.new(|cx| {
|
||||
let server_view = cx.new(|cx| {
|
||||
crate::acp::AcpServerView::new(
|
||||
server,
|
||||
resume_thread,
|
||||
@@ -1859,7 +1871,7 @@ impl AgentPanel {
|
||||
)
|
||||
});
|
||||
|
||||
self.set_active_view(ActiveView::AgentThread { thread_view }, true, window, cx);
|
||||
self.set_active_view(ActiveView::AgentThread { server_view }, true, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1867,7 +1879,7 @@ impl Focusable for AgentPanel {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
match &self.active_view {
|
||||
ActiveView::Uninitialized => self.focus_handle.clone(),
|
||||
ActiveView::AgentThread { thread_view, .. } => thread_view.focus_handle(cx),
|
||||
ActiveView::AgentThread { server_view, .. } => server_view.focus_handle(cx),
|
||||
ActiveView::History { kind } => match kind {
|
||||
HistoryKind::AgentThreads => self.acp_history.focus_handle(cx),
|
||||
HistoryKind::TextThreads => self.text_thread_history.focus_handle(cx),
|
||||
@@ -1988,13 +2000,13 @@ impl AgentPanel {
|
||||
const LOADING_SUMMARY_PLACEHOLDER: &str = "Loading Summary…";
|
||||
|
||||
let content = match &self.active_view {
|
||||
ActiveView::AgentThread { thread_view } => {
|
||||
let is_generating_title = thread_view
|
||||
ActiveView::AgentThread { server_view } => {
|
||||
let is_generating_title = server_view
|
||||
.read(cx)
|
||||
.as_native_thread(cx)
|
||||
.map_or(false, |t| t.read(cx).is_generating_title());
|
||||
|
||||
if let Some(title_editor) = thread_view
|
||||
if let Some(title_editor) = server_view
|
||||
.read(cx)
|
||||
.parent_thread(cx)
|
||||
.map(|r| r.read(cx).title_editor.clone())
|
||||
@@ -2002,7 +2014,7 @@ impl AgentPanel {
|
||||
let container = div()
|
||||
.w_full()
|
||||
.on_action({
|
||||
let thread_view = thread_view.downgrade();
|
||||
let thread_view = server_view.downgrade();
|
||||
move |_: &menu::Confirm, window, cx| {
|
||||
if let Some(thread_view) = thread_view.upgrade() {
|
||||
thread_view.focus_handle(cx).focus(window, cx);
|
||||
@@ -2010,7 +2022,7 @@ impl AgentPanel {
|
||||
}
|
||||
})
|
||||
.on_action({
|
||||
let thread_view = thread_view.downgrade();
|
||||
let thread_view = server_view.downgrade();
|
||||
move |_: &editor::actions::Cancel, window, cx| {
|
||||
if let Some(thread_view) = thread_view.upgrade() {
|
||||
thread_view.focus_handle(cx).focus(window, cx);
|
||||
@@ -2033,7 +2045,7 @@ impl AgentPanel {
|
||||
container.into_any_element()
|
||||
}
|
||||
} else {
|
||||
Label::new(thread_view.read(cx).title(cx))
|
||||
Label::new(server_view.read(cx).title(cx))
|
||||
.color(Color::Muted)
|
||||
.truncate()
|
||||
.into_any_element()
|
||||
@@ -2171,12 +2183,12 @@ impl AgentPanel {
|
||||
};
|
||||
|
||||
let thread_view = match &self.active_view {
|
||||
ActiveView::AgentThread { thread_view } => Some(thread_view.clone()),
|
||||
ActiveView::AgentThread { server_view } => Some(server_view.clone()),
|
||||
_ => None,
|
||||
};
|
||||
let thread_with_messages = match &self.active_view {
|
||||
ActiveView::AgentThread { thread_view } => {
|
||||
thread_view.read(cx).has_user_submitted_prompt(cx)
|
||||
ActiveView::AgentThread { server_view } => {
|
||||
server_view.read(cx).has_user_submitted_prompt(cx)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
@@ -2339,7 +2351,7 @@ impl AgentPanel {
|
||||
};
|
||||
|
||||
let active_thread = match &self.active_view {
|
||||
ActiveView::AgentThread { thread_view } => thread_view.read(cx).as_native_thread(cx),
|
||||
ActiveView::AgentThread { server_view } => server_view.read(cx).as_native_thread(cx),
|
||||
ActiveView::Uninitialized
|
||||
| ActiveView::TextThread { .. }
|
||||
| ActiveView::History { .. }
|
||||
@@ -2773,8 +2785,8 @@ impl AgentPanel {
|
||||
ActiveView::Uninitialized | ActiveView::History { .. } | ActiveView::Configuration => {
|
||||
false
|
||||
}
|
||||
ActiveView::AgentThread { thread_view, .. }
|
||||
if thread_view.read(cx).as_native_thread(cx).is_none() =>
|
||||
ActiveView::AgentThread { server_view, .. }
|
||||
if server_view.read(cx).as_native_thread(cx).is_none() =>
|
||||
{
|
||||
false
|
||||
}
|
||||
@@ -3046,8 +3058,8 @@ impl AgentPanel {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match &self.active_view {
|
||||
ActiveView::AgentThread { thread_view } => {
|
||||
thread_view.update(cx, |thread_view, cx| {
|
||||
ActiveView::AgentThread { server_view } => {
|
||||
server_view.update(cx, |thread_view, cx| {
|
||||
thread_view.insert_dragged_files(paths, added_worktrees, window, cx);
|
||||
});
|
||||
}
|
||||
@@ -3165,8 +3177,8 @@ impl Render for AgentPanel {
|
||||
|
||||
match &self.active_view {
|
||||
ActiveView::Uninitialized => parent,
|
||||
ActiveView::AgentThread { thread_view, .. } => parent
|
||||
.child(thread_view.clone())
|
||||
ActiveView::AgentThread { server_view, .. } => parent
|
||||
.child(server_view.clone())
|
||||
.child(self.render_drag_target(cx)),
|
||||
ActiveView::History { kind } => match kind {
|
||||
HistoryKind::AgentThreads => parent.child(self.acp_history.clone()),
|
||||
|
||||
@@ -37,6 +37,7 @@ pub enum AgentThreadStatus {
|
||||
struct AgentThreadInfo {
|
||||
title: SharedString,
|
||||
status: AgentThreadStatus,
|
||||
icon: IconName,
|
||||
}
|
||||
|
||||
const LAST_THREAD_TITLES_KEY: &str = "sidebar-last-thread-titles";
|
||||
@@ -99,6 +100,7 @@ impl WorkspaceThreadEntry {
|
||||
Some(AgentThreadInfo {
|
||||
title: SharedString::from(title.clone()),
|
||||
status: AgentThreadStatus::Completed,
|
||||
icon: IconName::ZedAgent,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -112,14 +114,23 @@ impl WorkspaceThreadEntry {
|
||||
|
||||
fn thread_info(workspace: &Entity<Workspace>, cx: &App) -> Option<AgentThreadInfo> {
|
||||
let agent_panel = workspace.read(cx).panel::<AgentPanel>(cx)?;
|
||||
let thread = agent_panel.read(cx).active_agent_thread(cx)?;
|
||||
let thread_ref = thread.read(cx);
|
||||
let title = thread_ref.title();
|
||||
let status = match thread_ref.status() {
|
||||
let agent_panel_ref = agent_panel.read(cx);
|
||||
|
||||
let thread_view = agent_panel_ref.as_active_thread_view(cx)?.read(cx);
|
||||
let thread = thread_view.thread.read(cx);
|
||||
|
||||
let icon = thread_view.agent_icon;
|
||||
let title = thread.title();
|
||||
|
||||
let status = match thread.status() {
|
||||
ThreadStatus::Generating => AgentThreadStatus::Running,
|
||||
ThreadStatus::Idle => AgentThreadStatus::Completed,
|
||||
};
|
||||
Some(AgentThreadInfo { title, status })
|
||||
Some(AgentThreadInfo {
|
||||
title,
|
||||
status,
|
||||
icon,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,7 +605,6 @@ impl PickerDelegate for WorkspacePickerDelegate {
|
||||
let workspace_index = thread_entry.index;
|
||||
let multi_workspace = self.multi_workspace.clone();
|
||||
let workspace_count = self.multi_workspace.read(cx).workspaces().len();
|
||||
let is_active = self.active_workspace_index == workspace_index;
|
||||
let is_hovered = self.hovered_thread_item == Some(workspace_index);
|
||||
|
||||
let remove_btn = IconButton::new(
|
||||
@@ -628,11 +638,11 @@ impl PickerDelegate for WorkspacePickerDelegate {
|
||||
("workspace-item", thread_entry.index),
|
||||
thread_subtitle.unwrap_or("New Thread".into()),
|
||||
)
|
||||
.icon(if is_active {
|
||||
IconName::FolderOpen
|
||||
} else {
|
||||
IconName::Folder
|
||||
})
|
||||
.icon(
|
||||
thread_info
|
||||
.as_ref()
|
||||
.map_or(IconName::ZedAgent, |info| info.icon),
|
||||
)
|
||||
.running(running)
|
||||
.generation_done(has_notification)
|
||||
.selected(selected)
|
||||
@@ -859,8 +869,14 @@ impl Sidebar {
|
||||
title: SharedString,
|
||||
status: AgentThreadStatus,
|
||||
) {
|
||||
self.test_thread_infos
|
||||
.insert(index, AgentThreadInfo { title, status });
|
||||
self.test_thread_infos.insert(
|
||||
index,
|
||||
AgentThreadInfo {
|
||||
title,
|
||||
status,
|
||||
icon: IconName::ZedAgent,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
||||
Reference in new issue
Block a user