From 440bb2ff04bb04e1e6879c379035d30197d3b71d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 4 Nov 2025 14:10:47 -0800 Subject: [PATCH] Add basic agents view Co-authored-by: Nathan Sobo --- crates/agent_ui/src/agent_ui.rs | 2 + crates/agent_ui/src/agents_panel.rs | 181 ++++++++++++++++++++++++++++ crates/workspace/src/dock.rs | 9 +- crates/zed/src/zed.rs | 5 +- 4 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 crates/agent_ui/src/agents_panel.rs diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index 7869aa4e0191f393a05ff1b2c0307bccaef41dc8..922dcf540d5411a30029dde10bbe35514e77aa50 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -3,6 +3,7 @@ mod agent_configuration; mod agent_diff; mod agent_model_selector; mod agent_panel; +pub mod agents_panel; mod buffer_codegen; mod context; mod context_picker; @@ -249,6 +250,7 @@ pub fn init( cx: &mut App, ) { AgentSettings::register(cx); + agents_panel::init(cx); assistant_text_thread::init(client.clone(), cx); rules_library::init(cx); diff --git a/crates/agent_ui/src/agents_panel.rs b/crates/agent_ui/src/agents_panel.rs new file mode 100644 index 0000000000000000000000000000000000000000..fbfbe16a4e15de8fc0b8c3e7f45af172c1683d46 --- /dev/null +++ b/crates/agent_ui/src/agents_panel.rs @@ -0,0 +1,181 @@ +use gpui::{EventEmitter, Focusable, actions}; +use ui::{ + App, Context, IconName, IntoElement, Label, LabelCommon as _, LabelSize, ListItem, + ListItemSpacing, ParentElement, Render, RenderOnce, Styled, Toggleable as _, Window, div, + h_flex, px, +}; +use workspace::{Panel, Workspace, dock::PanelEvent}; + +actions!( + agents, + [ + /// Toggle the visibility of the agents panel. + ToggleAgentsPanel + ] +); + +pub fn init(cx: &mut App) { + // init_settings(cx); + + cx.observe_new(|workspace: &mut Workspace, _, _| { + workspace.register_action(|workspace, _: &ToggleAgentsPanel, window, cx| { + workspace.toggle_panel_focus::(window, cx); + }); + }) + .detach(); +} + +pub struct AgentsPanel { + focus_handle: gpui::FocusHandle, +} + +impl AgentsPanel { + pub fn new(cx: &mut ui::Context) -> Self { + let focus_handle = cx.focus_handle(); + Self { focus_handle } + } +} + +impl Panel for AgentsPanel { + fn persistent_name() -> &'static str { + "AgentsPanel" + } + + fn panel_key() -> &'static str { + "AgentsPanel" + } + + fn position(&self, window: &ui::Window, cx: &ui::App) -> workspace::dock::DockPosition { + workspace::dock::DockPosition::Left + } + + fn position_is_valid(&self, position: workspace::dock::DockPosition) -> bool { + match position { + workspace::dock::DockPosition::Left | workspace::dock::DockPosition::Right => true, + workspace::dock::DockPosition::Bottom => false, + } + } + + fn set_position( + &mut self, + _position: workspace::dock::DockPosition, + _window: &mut ui::Window, + _cx: &mut ui::Context, + ) { + // TODO! + } + + fn size(&self, _window: &ui::Window, _cx: &ui::App) -> ui::Pixels { + // TODO! + px(300.0) + } + + fn set_size( + &mut self, + _size: Option, + _window: &mut ui::Window, + _cx: &mut ui::Context, + ) { + // TODO! + } + + fn icon(&self, _window: &ui::Window, _cx: &ui::App) -> Option { + //todo! + Some(IconName::ZedAssistant) + } + + fn icon_tooltip(&self, _window: &ui::Window, _cx: &ui::App) -> Option<&'static str> { + //todo! + Some("Agents panel") + } + + fn toggle_action(&self) -> Box { + Box::new(ToggleAgentsPanel) + } + + fn activation_priority(&self) -> u32 { + 1 + } + + fn starts_open(&self, _window: &Window, _cx: &App) -> bool { + true + } + fn enabled(&self, _cx: &App) -> bool { + true + } +} + +impl EventEmitter for AgentsPanel {} + +impl Focusable for AgentsPanel { + fn focus_handle(&self, _cx: &ui::App) -> gpui::FocusHandle { + self.focus_handle.clone() + } +} + +#[derive(IntoElement)] +struct AgentThreadSummary { + title: gpui::SharedString, + worktree_branch: Option, + diff: AgentThreadDiff, +} + +#[derive(IntoElement)] +struct AgentThreadDiff { + removed: usize, + modified: usize, + added: usize, +} + +impl Render for AgentsPanel { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let agent_threads = vec![ + AgentThreadSummary { + title: "Building the agents panel".into(), + worktree_branch: Some("new-threads-pane".into()), + diff: AgentThreadDiff { + removed: 0, + modified: 0, + added: 1, + }, + }, + AgentThreadSummary { + title: "Integrate Delta DB".into(), + worktree_branch: Some("integrate-deltadb".into()), + diff: AgentThreadDiff { + removed: 2, + modified: 10, + added: 3, + }, + }, + ]; + + div().size_full().children(agent_threads) + } +} + +impl RenderOnce for AgentThreadSummary { + fn render(self, _window: &mut Window, _cx: &mut ui::App) -> impl IntoElement { + ListItem::new("list-item") + .rounded() + .spacing(ListItemSpacing::Sparse) + .start_slot( + h_flex() + .w_full() + .gap_2() + .justify_between() + .child(Label::new(self.title).size(LabelSize::Default).truncate()) + .children( + self.worktree_branch + .map(|branch| Label::new(branch).size(LabelSize::Small).truncate()), + ) + .child(self.diff), + ) + } +} + +impl RenderOnce for AgentThreadDiff { + fn render(self, _window: &mut Window, _cx: &mut ui::App) -> impl IntoElement { + Label::new(format!("{}:{}:{}", self.added, self.modified, self.removed)) + } +} diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 05af5d080c4c965f3d53f61b5af144a456ce0074..2ac08e45bcc051eac14e16b82729aa22d25ca517 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -13,6 +13,7 @@ use settings::SettingsStore; use std::sync::Arc; use ui::{ContextMenu, Divider, DividerColor, IconButton, Tooltip, h_flex}; use ui::{prelude::*, right_click_menu}; +use util::ResultExt as _; pub(crate) const RESIZE_HANDLE_SIZE: Pixels = px(6.); @@ -882,7 +883,13 @@ impl Render for PanelButtons { .enumerate() .filter_map(|(i, entry)| { let icon = entry.panel.icon(window, cx)?; - let icon_tooltip = entry.panel.icon_tooltip(window, cx)?; + let icon_tooltip = entry + .panel + .icon_tooltip(window, cx) + .ok_or_else(|| { + anyhow::anyhow!("can't render a panel button without an icon tooltip") + }) + .log_err()?; let name = entry.panel.persistent_name(); let panel = entry.panel.clone(); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 2d7d47e968e93eef3d455cec9c324a4d4e0cff42..5354e7896d42d8b01764641193888eeb5c74b789 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -9,6 +9,7 @@ mod quick_action_bar; #[cfg(target_os = "windows")] pub(crate) mod windows_only_instance; +use agent_ui::agents_panel::AgentsPanel; use agent_ui::{AgentDiffToolbar, AgentPanelDelegate}; use anyhow::Context as _; pub use app_menus::*; @@ -590,6 +591,7 @@ fn initialize_panels( cx.clone(), ); let debug_panel = DebugPanel::load(workspace_handle.clone(), cx); + let agents_panel = cx.new(|cx| AgentsPanel::new(cx)).unwrap(); let ( project_panel, @@ -611,9 +613,10 @@ fn initialize_panels( workspace_handle.update_in(cx, |workspace, window, cx| { workspace.add_panel(project_panel, window, cx); + workspace.add_panel(agents_panel, window, cx); + workspace.add_panel(git_panel, window, cx); workspace.add_panel(outline_panel, window, cx); workspace.add_panel(terminal_panel, window, cx); - workspace.add_panel(git_panel, window, cx); workspace.add_panel(channels_panel, window, cx); workspace.add_panel(notification_panel, window, cx); workspace.add_panel(debug_panel, window, cx);