assistant2: Sketch in toolbar (#21114)

Marshall Bowers created

This PR sketches in the toolbar for `assistant2`.

<img width="1136" alt="Screenshot 2024-11-23 at 12 39 49 PM"
src="https://github.com/user-attachments/assets/ed56fc36-54c8-48d4-8446-6c1f182fcef2">

Release Notes:

- N/A

Change summary

Cargo.lock                               |   2 
crates/assistant2/Cargo.toml             |   2 
crates/assistant2/src/assistant.rs       |   2 
crates/assistant2/src/assistant_panel.rs | 125 +++++++++++++++++++++++++
4 files changed, 126 insertions(+), 5 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -458,6 +458,8 @@ dependencies = [
  "command_palette_hooks",
  "feature_flags",
  "gpui",
+ "language_model",
+ "language_model_selector",
  "proto",
  "ui",
  "workspace",

crates/assistant2/Cargo.toml 🔗

@@ -17,6 +17,8 @@ anyhow.workspace = true
 command_palette_hooks.workspace = true
 feature_flags.workspace = true
 gpui.workspace = true
+language_model.workspace = true
+language_model_selector.workspace = true
 proto.workspace = true
 ui.workspace = true
 workspace.workspace = true

crates/assistant2/src/assistant.rs 🔗

@@ -6,7 +6,7 @@ use gpui::{actions, AppContext};
 
 pub use crate::assistant_panel::AssistantPanel;
 
-actions!(assistant2, [ToggleFocus, NewChat]);
+actions!(assistant2, [ToggleFocus, NewChat, ToggleModelSelector]);
 
 const NAMESPACE: &str = "assistant2";
 

crates/assistant2/src/assistant_panel.rs 🔗

@@ -3,11 +3,13 @@ use gpui::{
     prelude::*, px, Action, AppContext, AsyncWindowContext, EventEmitter, FocusHandle,
     FocusableView, Pixels, Task, View, ViewContext, WeakView, WindowContext,
 };
-use ui::prelude::*;
+use language_model::LanguageModelRegistry;
+use language_model_selector::LanguageModelSelector;
+use ui::{prelude::*, ButtonLike, Divider, IconButtonShape, Tab, Tooltip};
 use workspace::dock::{DockPosition, Panel, PanelEvent};
 use workspace::{Pane, Workspace};
 
-use crate::{NewChat, ToggleFocus};
+use crate::{NewChat, ToggleFocus, ToggleModelSelector};
 
 pub fn init(cx: &mut AppContext) {
     cx.observe_new_views(
@@ -116,8 +118,123 @@ impl Panel for AssistantPanel {
     }
 }
 
+impl AssistantPanel {
+    fn render_toolbar(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        let focus_handle = self.focus_handle(cx);
+
+        h_flex()
+            .id("assistant-toolbar")
+            .justify_between()
+            .gap(DynamicSpacing::Base08.rems(cx))
+            .h(Tab::container_height(cx))
+            .px(DynamicSpacing::Base08.rems(cx))
+            .bg(cx.theme().colors().tab_bar_background)
+            .border_b_1()
+            .border_color(cx.theme().colors().border_variant)
+            .child(h_flex().child(Label::new("Chat Title Goes Here")))
+            .child(
+                h_flex()
+                    .gap(DynamicSpacing::Base08.rems(cx))
+                    .child(self.render_language_model_selector(cx))
+                    .child(Divider::vertical())
+                    .child(
+                        IconButton::new("new-chat", IconName::Plus)
+                            .shape(IconButtonShape::Square)
+                            .icon_size(IconSize::Small)
+                            .style(ButtonStyle::Subtle)
+                            .tooltip({
+                                let focus_handle = focus_handle.clone();
+                                move |cx| {
+                                    Tooltip::for_action_in("New Chat", &NewChat, &focus_handle, cx)
+                                }
+                            })
+                            .on_click(move |_event, _cx| {
+                                println!("New Chat");
+                            }),
+                    )
+                    .child(
+                        IconButton::new("open-history", IconName::HistoryRerun)
+                            .shape(IconButtonShape::Square)
+                            .icon_size(IconSize::Small)
+                            .style(ButtonStyle::Subtle)
+                            .tooltip(move |cx| Tooltip::text("Open History", cx))
+                            .on_click(move |_event, _cx| {
+                                println!("Open History");
+                            }),
+                    )
+                    .child(
+                        IconButton::new("configure-assistant", IconName::Settings)
+                            .shape(IconButtonShape::Square)
+                            .icon_size(IconSize::Small)
+                            .style(ButtonStyle::Subtle)
+                            .tooltip(move |cx| Tooltip::text("Configure Assistant", cx))
+                            .on_click(move |_event, _cx| {
+                                println!("Configure Assistant");
+                            }),
+                    ),
+            )
+    }
+
+    fn render_language_model_selector(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
+        let active_model = LanguageModelRegistry::read_global(cx).active_model();
+
+        LanguageModelSelector::new(
+            |model, _cx| {
+                println!("Selected {:?}", model.name());
+            },
+            ButtonLike::new("active-model")
+                .style(ButtonStyle::Subtle)
+                .child(
+                    h_flex()
+                        .w_full()
+                        .gap_0p5()
+                        .child(
+                            div()
+                                .overflow_x_hidden()
+                                .flex_grow()
+                                .whitespace_nowrap()
+                                .child(match (active_provider, active_model) {
+                                    (Some(provider), Some(model)) => h_flex()
+                                        .gap_1()
+                                        .child(
+                                            Icon::new(
+                                                model.icon().unwrap_or_else(|| provider.icon()),
+                                            )
+                                            .color(Color::Muted)
+                                            .size(IconSize::XSmall),
+                                        )
+                                        .child(
+                                            Label::new(model.name().0)
+                                                .size(LabelSize::Small)
+                                                .color(Color::Muted),
+                                        )
+                                        .into_any_element(),
+                                    _ => Label::new("No model selected")
+                                        .size(LabelSize::Small)
+                                        .color(Color::Muted)
+                                        .into_any_element(),
+                                }),
+                        )
+                        .child(
+                            Icon::new(IconName::ChevronDown)
+                                .color(Color::Muted)
+                                .size(IconSize::XSmall),
+                        ),
+                )
+                .tooltip(move |cx| Tooltip::for_action("Change Model", &ToggleModelSelector, cx)),
+        )
+    }
+}
+
 impl Render for AssistantPanel {
-    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
-        div().child(Label::new("Assistant II"))
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        v_flex()
+            .key_context("AssistantPanel2")
+            .size_full()
+            .on_action(cx.listener(|_this, _: &NewChat, _cx| {
+                println!("Action: New Chat");
+            }))
+            .child(self.render_toolbar(cx))
     }
 }