Add basic agents view

Mikayla Maki and Nathan Sobo created

Co-authored-by: Nathan Sobo <nathan@zed.dev>

Change summary

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(-)

Detailed changes

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);

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::<AgentsPanel>(window, cx);
+        });
+    })
+    .detach();
+}
+
+pub struct AgentsPanel {
+    focus_handle: gpui::FocusHandle,
+}
+
+impl AgentsPanel {
+    pub fn new(cx: &mut ui::Context<Self>) -> 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<Self>,
+    ) {
+        // TODO!
+    }
+
+    fn size(&self, _window: &ui::Window, _cx: &ui::App) -> ui::Pixels {
+        // TODO!
+        px(300.0)
+    }
+
+    fn set_size(
+        &mut self,
+        _size: Option<ui::Pixels>,
+        _window: &mut ui::Window,
+        _cx: &mut ui::Context<Self>,
+    ) {
+        // TODO!
+    }
+
+    fn icon(&self, _window: &ui::Window, _cx: &ui::App) -> Option<ui::IconName> {
+        //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<dyn gpui::Action> {
+        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<PanelEvent> 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<gpui::SharedString>,
+    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<Self>) -> 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))
+    }
+}

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();
 

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);