assets/images/acp_logo_serif.svg 🔗
@@ -1,2 +1,46 @@
-
Bennet Bo Fenner and Danilo Leal created
Release Notes:
- N/A
---------
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
assets/images/acp_logo_serif.svg | 1
crates/agent_ui/src/agent_panel.rs | 7
crates/agent_ui/src/ui.rs | 2
crates/agent_ui/src/ui/acp_onboarding_modal.rs | 20
crates/agent_ui/src/ui/claude_code_onboarding_modal.rs | 254 ++++++++++++
crates/title_bar/src/onboarding_banner.rs | 11
crates/title_bar/src/title_bar.rs | 10
crates/zed_actions/src/lib.rs | 2
8 files changed, 284 insertions(+), 23 deletions(-)
@@ -1,2 +1,46 @@
-
@@ -10,11 +10,11 @@ use agent2::{DbThreadMetadata, HistoryEntry};
use db::kvp::{Dismissable, KEY_VALUE_STORE};
use serde::{Deserialize, Serialize};
use zed_actions::OpenBrowser;
-use zed_actions::agent::ReauthenticateAgent;
+use zed_actions::agent::{OpenClaudeCodeOnboardingModal, ReauthenticateAgent};
use crate::acp::{AcpThreadHistory, ThreadHistoryEvent};
use crate::agent_diff::AgentDiffThread;
-use crate::ui::AcpOnboardingModal;
+use crate::ui::{AcpOnboardingModal, ClaudeCodeOnboardingModal};
use crate::{
AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode,
DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
@@ -207,6 +207,9 @@ pub fn init(cx: &mut App) {
.register_action(|workspace, _: &OpenAcpOnboardingModal, window, cx| {
AcpOnboardingModal::toggle(workspace, window, cx)
})
+ .register_action(|workspace, _: &OpenClaudeCodeOnboardingModal, window, cx| {
+ ClaudeCodeOnboardingModal::toggle(workspace, window, cx)
+ })
.register_action(|_workspace, _: &ResetOnboarding, window, cx| {
window.dispatch_action(workspace::RestoreBanner.boxed_clone(), cx);
window.refresh();
@@ -1,6 +1,7 @@
mod acp_onboarding_modal;
mod agent_notification;
mod burn_mode_tooltip;
+mod claude_code_onboarding_modal;
mod context_pill;
mod end_trial_upsell;
mod onboarding_modal;
@@ -10,6 +11,7 @@ mod unavailable_editing_tooltip;
pub use acp_onboarding_modal::*;
pub use agent_notification::*;
pub use burn_mode_tooltip::*;
+pub use claude_code_onboarding_modal::*;
pub use context_pill::*;
pub use end_trial_upsell::*;
pub use onboarding_modal::*;
@@ -141,20 +141,12 @@ impl Render for AcpOnboardingModal {
.bg(gpui::black().opacity(0.15)),
)
.child(
- h_flex()
- .gap_4()
- .child(
- Vector::new(VectorName::AcpLogo, rems_from_px(106.), rems_from_px(40.))
- .color(ui::Color::Custom(cx.theme().colors().text.opacity(0.8))),
- )
- .child(
- Vector::new(
- VectorName::AcpLogoSerif,
- rems_from_px(111.),
- rems_from_px(41.),
- )
- .color(ui::Color::Custom(cx.theme().colors().text.opacity(0.8))),
- ),
+ Vector::new(
+ VectorName::AcpLogoSerif,
+ rems_from_px(257.),
+ rems_from_px(47.),
+ )
+ .color(ui::Color::Custom(cx.theme().colors().text.opacity(0.8))),
)
.child(
v_flex()
@@ -0,0 +1,254 @@
+use client::zed_urls;
+use gpui::{
+ ClickEvent, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, MouseDownEvent, Render,
+ linear_color_stop, linear_gradient,
+};
+use ui::{TintColor, Vector, VectorName, prelude::*};
+use workspace::{ModalView, Workspace};
+
+use crate::agent_panel::{AgentPanel, AgentType};
+
+macro_rules! claude_code_onboarding_event {
+ ($name:expr) => {
+ telemetry::event!($name, source = "ACP Claude Code Onboarding");
+ };
+ ($name:expr, $($key:ident $(= $value:expr)?),+ $(,)?) => {
+ telemetry::event!($name, source = "ACP Claude Code Onboarding", $($key $(= $value)?),+);
+ };
+}
+
+pub struct ClaudeCodeOnboardingModal {
+ focus_handle: FocusHandle,
+ workspace: Entity<Workspace>,
+}
+
+impl ClaudeCodeOnboardingModal {
+ pub fn toggle(workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>) {
+ let workspace_entity = cx.entity();
+ workspace.toggle_modal(window, cx, |_window, cx| Self {
+ workspace: workspace_entity,
+ focus_handle: cx.focus_handle(),
+ });
+ }
+
+ fn open_panel(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
+ self.workspace.update(cx, |workspace, cx| {
+ workspace.focus_panel::<AgentPanel>(window, cx);
+
+ if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+ panel.update(cx, |panel, cx| {
+ panel.new_agent_thread(AgentType::ClaudeCode, window, cx);
+ });
+ }
+ });
+
+ cx.emit(DismissEvent);
+
+ claude_code_onboarding_event!("Open Panel Clicked");
+ }
+
+ fn view_docs(&mut self, _: &ClickEvent, _: &mut Window, cx: &mut Context<Self>) {
+ cx.open_url(&zed_urls::external_agents_docs(cx));
+ cx.notify();
+
+ claude_code_onboarding_event!("Documentation Link Clicked");
+ }
+
+ fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
+ cx.emit(DismissEvent);
+ }
+}
+
+impl EventEmitter<DismissEvent> for ClaudeCodeOnboardingModal {}
+
+impl Focusable for ClaudeCodeOnboardingModal {
+ fn focus_handle(&self, _cx: &App) -> FocusHandle {
+ self.focus_handle.clone()
+ }
+}
+
+impl ModalView for ClaudeCodeOnboardingModal {}
+
+impl Render for ClaudeCodeOnboardingModal {
+ fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+ let illustration_element = |icon: IconName, label: Option<SharedString>, opacity: f32| {
+ h_flex()
+ .px_1()
+ .py_0p5()
+ .gap_1()
+ .rounded_sm()
+ .bg(cx.theme().colors().element_active.opacity(0.05))
+ .border_1()
+ .border_color(cx.theme().colors().border)
+ .border_dashed()
+ .child(
+ Icon::new(icon)
+ .size(IconSize::Small)
+ .color(Color::Custom(cx.theme().colors().text_muted.opacity(0.15))),
+ )
+ .map(|this| {
+ if let Some(label_text) = label {
+ this.child(
+ Label::new(label_text)
+ .size(LabelSize::Small)
+ .color(Color::Muted),
+ )
+ } else {
+ this.child(
+ div().w_16().h_1().rounded_full().bg(cx
+ .theme()
+ .colors()
+ .element_active
+ .opacity(0.6)),
+ )
+ }
+ })
+ .opacity(opacity)
+ };
+
+ let illustration = h_flex()
+ .relative()
+ .h(rems_from_px(126.))
+ .bg(cx.theme().colors().editor_background)
+ .border_b_1()
+ .border_color(cx.theme().colors().border_variant)
+ .justify_center()
+ .gap_8()
+ .rounded_t_md()
+ .overflow_hidden()
+ .child(
+ div().absolute().inset_0().w(px(515.)).h(px(126.)).child(
+ Vector::new(VectorName::AcpGrid, rems_from_px(515.), rems_from_px(126.))
+ .color(ui::Color::Custom(cx.theme().colors().text.opacity(0.02))),
+ ),
+ )
+ .child(div().absolute().inset_0().size_full().bg(linear_gradient(
+ 0.,
+ linear_color_stop(
+ cx.theme().colors().elevated_surface_background.opacity(0.1),
+ 0.9,
+ ),
+ linear_color_stop(
+ cx.theme().colors().elevated_surface_background.opacity(0.),
+ 0.,
+ ),
+ )))
+ .child(
+ div()
+ .absolute()
+ .inset_0()
+ .size_full()
+ .bg(gpui::black().opacity(0.15)),
+ )
+ .child(
+ Vector::new(
+ VectorName::AcpLogoSerif,
+ rems_from_px(257.),
+ rems_from_px(47.),
+ )
+ .color(ui::Color::Custom(cx.theme().colors().text.opacity(0.8))),
+ )
+ .child(
+ v_flex()
+ .gap_1p5()
+ .child(illustration_element(IconName::Stop, None, 0.15))
+ .child(illustration_element(
+ IconName::AiGemini,
+ Some("New Gemini CLI Thread".into()),
+ 0.3,
+ ))
+ .child(
+ h_flex()
+ .pl_1()
+ .pr_2()
+ .py_0p5()
+ .gap_1()
+ .rounded_sm()
+ .bg(cx.theme().colors().element_active.opacity(0.2))
+ .border_1()
+ .border_color(cx.theme().colors().border)
+ .child(
+ Icon::new(IconName::AiClaude)
+ .size(IconSize::Small)
+ .color(Color::Muted),
+ )
+ .child(Label::new("New Claude Code Thread").size(LabelSize::Small)),
+ )
+ .child(illustration_element(
+ IconName::Stop,
+ Some("Your Agent Here".into()),
+ 0.3,
+ ))
+ .child(illustration_element(IconName::Stop, None, 0.15)),
+ );
+
+ let heading = v_flex()
+ .w_full()
+ .gap_1()
+ .child(
+ Label::new("Beta Release")
+ .size(LabelSize::Small)
+ .color(Color::Muted),
+ )
+ .child(Headline::new("Claude Code: Natively in Zed").size(HeadlineSize::Large));
+
+ let copy = "Powered by the Agent Client Protocol, you can now run Claude Code as\na first-class citizen in Zed's agent panel.";
+
+ let open_panel_button = Button::new("open-panel", "Start with Claude Code")
+ .icon_size(IconSize::Indicator)
+ .style(ButtonStyle::Tinted(TintColor::Accent))
+ .full_width()
+ .on_click(cx.listener(Self::open_panel));
+
+ let docs_button = Button::new("add-other-agents", "Add Other Agents")
+ .icon(IconName::ArrowUpRight)
+ .icon_size(IconSize::Indicator)
+ .icon_color(Color::Muted)
+ .full_width()
+ .on_click(cx.listener(Self::view_docs));
+
+ let close_button = h_flex().absolute().top_2().right_2().child(
+ IconButton::new("cancel", IconName::Close).on_click(cx.listener(
+ |_, _: &ClickEvent, _window, cx| {
+ claude_code_onboarding_event!("Canceled", trigger = "X click");
+ cx.emit(DismissEvent);
+ },
+ )),
+ );
+
+ v_flex()
+ .id("acp-onboarding")
+ .key_context("AcpOnboardingModal")
+ .relative()
+ .w(rems(34.))
+ .h_full()
+ .elevation_3(cx)
+ .track_focus(&self.focus_handle(cx))
+ .overflow_hidden()
+ .on_action(cx.listener(Self::cancel))
+ .on_action(cx.listener(|_, _: &menu::Cancel, _window, cx| {
+ claude_code_onboarding_event!("Canceled", trigger = "Action");
+ cx.emit(DismissEvent);
+ }))
+ .on_any_mouse_down(cx.listener(|this, _: &MouseDownEvent, window, _cx| {
+ this.focus_handle.focus(window);
+ }))
+ .child(illustration)
+ .child(
+ v_flex()
+ .p_4()
+ .gap_2()
+ .child(heading)
+ .child(Label::new(copy).color(Color::Muted))
+ .child(
+ v_flex()
+ .w_full()
+ .mt_2()
+ .gap_1()
+ .child(open_panel_button)
+ .child(docs_button),
+ ),
+ )
+ .child(close_button)
+ }
+}
@@ -7,6 +7,7 @@ pub struct OnboardingBanner {
dismissed: bool,
source: String,
details: BannerDetails,
+ visible_when: Option<Box<dyn Fn(&mut App) -> bool>>,
}
#[derive(Clone)]
@@ -42,12 +43,18 @@ impl OnboardingBanner {
label: label.into(),
subtitle: subtitle.or(Some(SharedString::from("Introducing:"))),
},
+ visible_when: None,
dismissed: get_dismissed(source),
}
}
- fn should_show(&self, _cx: &mut App) -> bool {
- !self.dismissed
+ pub fn visible_when(mut self, predicate: impl Fn(&mut App) -> bool + 'static) -> Self {
+ self.visible_when = Some(Box::new(predicate));
+ self
+ }
+
+ fn should_show(&self, cx: &mut App) -> bool {
+ !self.dismissed && self.visible_when.as_ref().map_or(true, |f| f(cx))
}
fn dismiss(&mut self, cx: &mut Context<Self>) {
@@ -279,13 +279,15 @@ impl TitleBar {
let banner = cx.new(|cx| {
OnboardingBanner::new(
- "ACP Onboarding",
- IconName::Sparkle,
- "Bring Your Own Agent",
+ "ACP Claude Code Onboarding",
+ IconName::AiClaude,
+ "Claude Code",
Some("Introducing:".into()),
- zed_actions::agent::OpenAcpOnboardingModal.boxed_clone(),
+ zed_actions::agent::OpenClaudeCodeOnboardingModal.boxed_clone(),
cx,
)
+ // When updating this to a non-AI feature release, remove this line.
+ .visible_when(|cx| !project::DisableAiSettings::get_global(cx).disable_ai)
});
let platform_titlebar = cx.new(|cx| PlatformTitleBar::new(id, cx));
@@ -286,6 +286,8 @@ pub mod agent {
OpenOnboardingModal,
/// Opens the ACP onboarding modal.
OpenAcpOnboardingModal,
+ /// Opens the Claude Code onboarding modal.
+ OpenClaudeCodeOnboardingModal,
/// Resets the agent onboarding state.
ResetOnboarding,
/// Starts a chat conversation with the agent.