From 74179fac7ccd4b745a1c559e10cf111fe126e7ed Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Tue, 17 Feb 2026 15:36:22 -0800 Subject: [PATCH] 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 Co-authored-by: Anthony Eid --- crates/agent_ui/src/agent_panel.rs | 66 ++++++++++++++++++------------ crates/sidebar/src/sidebar.rs | 42 +++++++++++++------ 2 files changed, 68 insertions(+), 40 deletions(-) diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 89dee2b1bd669541a169fb4f042e0e44c5e15c3c..3a70896c351a4ae3510db2904a0bcd46259ca209 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -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, + server_view: Entity, }, TextThread { text_thread_editor: Entity, @@ -855,7 +855,7 @@ impl AgentPanel { pub(crate) fn active_thread_view(&self) -> Option<&Entity> { 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> { + match &self.active_view { + ActiveView::AgentThread { server_view } => Some(server_view), + _ => None, + } + } + + pub fn as_active_thread_view(&self, cx: &App) -> Option> { + let server_view = self.as_active_server_view()?; + server_view.read(cx).active_thread().cloned() + } + pub fn active_agent_thread(&self, cx: &App) -> Option> { 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> { 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, ) { 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()), diff --git a/crates/sidebar/src/sidebar.rs b/crates/sidebar/src/sidebar.rs index 9fd6c368800c0cf88b09af817883c1159230ab4a..e259b86d95d69ac36c7f0abef75d9577840320f3 100644 --- a/crates/sidebar/src/sidebar.rs +++ b/crates/sidebar/src/sidebar.rs @@ -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, cx: &App) -> Option { let agent_panel = workspace.read(cx).panel::(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"))]