Add refinements to the AI onboarding flow (#33738)

This includes making sure that both the agent panel and Zed's edit
prediction have a consistent narrative when it comes to onboarding users
into the AI features, considering the possible different plans and
conditions (such as being signed in/out, account age, etc.)

Release Notes:

- N/A

---------

Co-authored-by: Bennet Bo Fenner <53836821+bennetbo@users.noreply.github.com>
Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
This commit is contained in:
Danilo Leal 2025-07-18 13:25:36 -03:00 committed by GitHub
parent 9a20843ba2
commit 4476860664
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 1465 additions and 1215 deletions

View file

@ -11,7 +11,6 @@ use serde::{Deserialize, Serialize};
use crate::NewExternalAgentThread;
use crate::agent_diff::AgentDiffThread;
use crate::language_model_selector::ToggleModelSelector;
use crate::{
AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode,
DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
@ -28,7 +27,7 @@ use crate::{
render_remaining_tokens,
},
thread_history::{HistoryEntryElement, ThreadHistory},
ui::AgentOnboardingModal,
ui::{AgentOnboardingModal, EndTrialUpsell},
};
use agent::{
Thread, ThreadError, ThreadEvent, ThreadId, ThreadSummary, TokenUsageRatio,
@ -37,6 +36,7 @@ use agent::{
thread_store::{TextThreadStore, ThreadStore},
};
use agent_settings::{AgentDockPosition, AgentSettings, CompletionMode, DefaultView};
use ai_onboarding::AgentPanelOnboarding;
use anyhow::{Result, anyhow};
use assistant_context::{AssistantContext, ContextEvent, ContextSummary};
use assistant_slash_command::SlashCommandWorkingSet;
@ -48,13 +48,12 @@ use fs::Fs;
use gpui::{
Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, ClipboardItem,
Corner, DismissEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, Hsla,
KeyContext, Pixels, Subscription, Task, UpdateGlobal, WeakEntity, linear_color_stop,
linear_gradient, prelude::*, pulsating_between,
KeyContext, Pixels, Subscription, Task, UpdateGlobal, WeakEntity, prelude::*,
pulsating_between,
};
use language::LanguageRegistry;
use language_model::{
ConfigurationError, ConfiguredModel, LanguageModelProviderTosView, LanguageModelRegistry,
ZED_CLOUD_PROVIDER_ID,
};
use project::{Project, ProjectPath, Worktree};
use prompt_store::{PromptBuilder, PromptStore, UserPromptId};
@ -66,9 +65,8 @@ use theme::ThemeSettings;
use time::UtcOffset;
use ui::utils::WithRemSize;
use ui::{
Banner, Button, Callout, CheckboxWithLabel, ContextMenu, ElevationIndex, IconPosition,
KeyBinding, PopoverMenu, PopoverMenuHandle, ProgressBar, Tab, Tooltip, Vector, VectorName,
prelude::*,
Banner, Callout, ContextMenu, ElevationIndex, KeyBinding, PopoverMenu, PopoverMenuHandle,
ProgressBar, Tab, Tooltip, prelude::*,
};
use util::ResultExt as _;
use workspace::{
@ -77,7 +75,7 @@ use workspace::{
};
use zed_actions::{
DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize,
agent::{OpenConfiguration, OpenOnboardingModal, ResetOnboarding},
agent::{OpenConfiguration, OpenOnboardingModal, ResetOnboarding, ToggleModelSelector},
assistant::{OpenRulesLibrary, ToggleFocus},
};
use zed_llm_client::{CompletionIntent, UsageLimit};
@ -188,7 +186,7 @@ pub fn init(cx: &mut App) {
window.refresh();
})
.register_action(|_workspace, _: &ResetTrialUpsell, _window, cx| {
Upsell::set_dismissed(false, cx);
OnboardingUpsell::set_dismissed(false, cx);
})
.register_action(|_workspace, _: &ResetTrialEndUpsell, _window, cx| {
TrialEndUpsell::set_dismissed(false, cx);
@ -453,7 +451,7 @@ pub struct AgentPanel {
height: Option<Pixels>,
zoomed: bool,
pending_serialization: Option<Task<Result<()>>>,
hide_upsell: bool,
onboarding: Entity<AgentPanelOnboarding>,
}
impl AgentPanel {
@ -555,6 +553,7 @@ impl AgentPanel {
let user_store = workspace.app_state().user_store.clone();
let project = workspace.project();
let language_registry = project.read(cx).languages().clone();
let client = workspace.client().clone();
let workspace = workspace.weak_handle();
let weak_self = cx.entity().downgrade();
@ -688,6 +687,17 @@ impl AgentPanel {
},
);
let onboarding = cx.new(|cx| {
AgentPanelOnboarding::new(
user_store.clone(),
client,
|_window, cx| {
OnboardingUpsell::set_dismissed(true, cx);
},
cx,
)
});
Self {
active_view,
workspace,
@ -719,7 +729,7 @@ impl AgentPanel {
height: None,
zoomed: false,
pending_serialization: None,
hide_upsell: false,
onboarding,
}
}
@ -2178,191 +2188,78 @@ impl AgentPanel {
return false;
}
match &self.active_view {
ActiveView::Thread { thread, .. } => {
if thread
.read(cx)
.thread()
.read(cx)
.configured_model()
.map_or(false, |model| {
model.provider.id() != language_model::ZED_CLOUD_PROVIDER_ID
})
{
return false;
}
}
ActiveView::TextThread { .. } => {
if LanguageModelRegistry::global(cx)
.read(cx)
.default_model()
.map_or(false, |model| {
model.provider.id() != language_model::ZED_CLOUD_PROVIDER_ID
})
{
return false;
}
}
ActiveView::ExternalAgentThread { .. }
| ActiveView::History
| ActiveView::Configuration => return false,
}
let plan = self.user_store.read(cx).current_plan();
let has_previous_trial = self.user_store.read(cx).trial_started_at().is_some();
matches!(plan, Some(Plan::Free)) && has_previous_trial
}
fn should_render_upsell(&self, cx: &mut Context<Self>) -> bool {
fn should_render_onboarding(&self, cx: &mut Context<Self>) -> bool {
if OnboardingUpsell::dismissed() {
return false;
}
match &self.active_view {
ActiveView::Thread { thread, .. } => {
let is_using_zed_provider = thread
.read(cx)
.thread()
.read(cx)
.configured_model()
.map_or(false, |model| model.provider.id() == ZED_CLOUD_PROVIDER_ID);
if !is_using_zed_provider {
return false;
}
}
ActiveView::ExternalAgentThread { .. } => {
return false;
}
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {
return false;
}
};
if self.hide_upsell || Upsell::dismissed() {
return false;
ActiveView::Thread { thread, .. } => thread
.read(cx)
.thread()
.read(cx)
.configured_model()
.map_or(true, |model| {
model.provider.id() == language_model::ZED_CLOUD_PROVIDER_ID
}),
ActiveView::TextThread { .. } => LanguageModelRegistry::global(cx)
.read(cx)
.default_model()
.map_or(true, |model| {
model.provider.id() == language_model::ZED_CLOUD_PROVIDER_ID
}),
ActiveView::ExternalAgentThread { .. }
| ActiveView::History
| ActiveView::Configuration => false,
}
let plan = self.user_store.read(cx).current_plan();
if matches!(plan, Some(Plan::ZedPro | Plan::ZedProTrial)) {
return false;
}
let has_previous_trial = self.user_store.read(cx).trial_started_at().is_some();
if has_previous_trial {
return false;
}
true
}
fn render_upsell(
fn render_onboarding(
&self,
_window: &mut Window,
cx: &mut Context<Self>,
) -> Option<impl IntoElement> {
if !self.should_render_upsell(cx) {
if !self.should_render_onboarding(cx) {
return None;
}
if self.user_store.read(cx).account_too_young() {
Some(self.render_young_account_upsell(cx).into_any_element())
} else {
Some(self.render_trial_upsell(cx).into_any_element())
}
}
fn render_young_account_upsell(&self, cx: &mut Context<Self>) -> impl IntoElement {
let checkbox = CheckboxWithLabel::new(
"dont-show-again",
Label::new("Don't show again").color(Color::Muted),
ToggleState::Unselected,
move |toggle_state, _window, cx| {
let toggle_state_bool = toggle_state.selected();
Upsell::set_dismissed(toggle_state_bool, cx);
},
);
let contents = div()
.size_full()
.gap_2()
.flex()
.flex_col()
.child(Headline::new("Build better with Zed Pro").size(HeadlineSize::Small))
.child(
Label::new("Your GitHub account was created less than 30 days ago, so we can't offer you a free trial.")
.size(LabelSize::Small),
)
.child(
Label::new(
"Use your own API keys, upgrade to Zed Pro or send an email to billing-support@zed.dev.",
)
.color(Color::Muted),
)
.child(
h_flex()
.w_full()
.px_neg_1()
.justify_between()
.items_center()
.child(h_flex().items_center().gap_1().child(checkbox))
.child(
h_flex()
.gap_2()
.child(
Button::new("dismiss-button", "Not Now")
.style(ButtonStyle::Transparent)
.color(Color::Muted)
.on_click({
let agent_panel = cx.entity();
move |_, _, cx| {
agent_panel.update(cx, |this, cx| {
this.hide_upsell = true;
cx.notify();
});
}
}),
)
.child(
Button::new("cta-button", "Upgrade to Zed Pro")
.style(ButtonStyle::Transparent)
.on_click(|_, _, cx| cx.open_url(&zed_urls::account_url(cx))),
),
),
);
self.render_upsell_container(cx, contents)
}
fn render_trial_upsell(&self, cx: &mut Context<Self>) -> impl IntoElement {
let checkbox = CheckboxWithLabel::new(
"dont-show-again",
Label::new("Don't show again").color(Color::Muted),
ToggleState::Unselected,
move |toggle_state, _window, cx| {
let toggle_state_bool = toggle_state.selected();
Upsell::set_dismissed(toggle_state_bool, cx);
},
);
let contents = div()
.size_full()
.gap_2()
.flex()
.flex_col()
.child(Headline::new("Build better with Zed Pro").size(HeadlineSize::Small))
.child(
Label::new("Try Zed Pro for free for 14 days - no credit card required.")
.size(LabelSize::Small),
)
.child(
Label::new(
"Use your own API keys or enable usage-based billing once you hit the cap.",
)
.color(Color::Muted),
)
.child(
h_flex()
.w_full()
.px_neg_1()
.justify_between()
.items_center()
.child(h_flex().items_center().gap_1().child(checkbox))
.child(
h_flex()
.gap_2()
.child(
Button::new("dismiss-button", "Not Now")
.style(ButtonStyle::Transparent)
.color(Color::Muted)
.on_click({
let agent_panel = cx.entity();
move |_, _, cx| {
agent_panel.update(cx, |this, cx| {
this.hide_upsell = true;
cx.notify();
});
}
}),
)
.child(
Button::new("cta-button", "Start Trial")
.style(ButtonStyle::Transparent)
.on_click(|_, _, cx| cx.open_url(&zed_urls::account_url(cx))),
),
),
);
self.render_upsell_container(cx, contents)
Some(div().size_full().child(self.onboarding.clone()))
}
fn render_trial_end_upsell(
@ -2374,141 +2271,15 @@ impl AgentPanel {
return None;
}
Some(
self.render_upsell_container(
cx,
div()
.size_full()
.gap_2()
.flex()
.flex_col()
.child(
Headline::new("Your Zed Pro trial has expired.").size(HeadlineSize::Small),
)
.child(
Label::new("You've been automatically reset to the free plan.")
.size(LabelSize::Small),
)
.child(
h_flex()
.w_full()
.px_neg_1()
.justify_between()
.items_center()
.child(div())
.child(
h_flex()
.gap_2()
.child(
Button::new("dismiss-button", "Stay on Free")
.style(ButtonStyle::Transparent)
.color(Color::Muted)
.on_click({
let agent_panel = cx.entity();
move |_, _, cx| {
agent_panel.update(cx, |_this, cx| {
TrialEndUpsell::set_dismissed(true, cx);
cx.notify();
});
}
}),
)
.child(
Button::new("cta-button", "Upgrade to Zed Pro")
.style(ButtonStyle::Transparent)
.on_click(|_, _, cx| {
cx.open_url(&zed_urls::account_url(cx))
}),
),
),
),
),
)
}
fn render_upsell_container(&self, cx: &mut Context<Self>, content: Div) -> Div {
div().p_2().child(
v_flex()
.w_full()
.elevation_2(cx)
.rounded(px(8.))
.bg(cx.theme().colors().background.alpha(0.5))
.p(px(3.))
.child(
div()
.gap_2()
.flex()
.flex_col()
.size_full()
.border_1()
.rounded(px(5.))
.border_color(cx.theme().colors().text.alpha(0.1))
.overflow_hidden()
.relative()
.bg(cx.theme().colors().panel_background)
.px_4()
.py_3()
.child(
div()
.absolute()
.top_0()
.right(px(-1.0))
.w(px(441.))
.h(px(167.))
.child(
Vector::new(
VectorName::Grid,
rems_from_px(441.),
rems_from_px(167.),
)
.color(ui::Color::Custom(cx.theme().colors().text.alpha(0.1))),
),
)
.child(
div()
.absolute()
.top(px(-8.0))
.right_0()
.w(px(400.))
.h(px(92.))
.child(
Vector::new(
VectorName::AiGrid,
rems_from_px(400.),
rems_from_px(92.),
)
.color(ui::Color::Custom(cx.theme().colors().text.alpha(0.32))),
),
)
// .child(
// div()
// .absolute()
// .top_0()
// .right(px(360.))
// .size(px(401.))
// .overflow_hidden()
// .bg(cx.theme().colors().panel_background)
// )
.child(
div()
.absolute()
.top_0()
.right_0()
.w(px(660.))
.h(px(401.))
.overflow_hidden()
.bg(linear_gradient(
75.,
linear_color_stop(
cx.theme().colors().panel_background.alpha(0.01),
1.0,
),
linear_color_stop(cx.theme().colors().panel_background, 0.45),
)),
)
.child(content),
),
)
Some(EndTrialUpsell::new(Arc::new({
let this = cx.entity();
move |_, cx| {
this.update(cx, |_this, cx| {
TrialEndUpsell::set_dismissed(true, cx);
cx.notify();
});
}
})))
}
fn render_thread_empty_state(
@ -2521,8 +2292,10 @@ impl AgentPanel {
.update(cx, |this, cx| this.recent_entries(6, cx));
let model_registry = LanguageModelRegistry::read_global(cx);
let configuration_error =
model_registry.configuration_error(model_registry.default_model(), cx);
let no_error = configuration_error.is_none();
let focus_handle = self.focus_handle(cx);
@ -2530,11 +2303,9 @@ impl AgentPanel {
.size_full()
.bg(cx.theme().colors().panel_background)
.when(recent_history.is_empty(), |this| {
let configuration_error_ref = &configuration_error;
this.child(
v_flex()
.size_full()
.max_w_80()
.mx_auto()
.justify_center()
.items_center()
@ -2542,137 +2313,91 @@ impl AgentPanel {
.child(h_flex().child(Headline::new("Welcome to the Agent Panel")))
.when(no_error, |parent| {
parent
.child(h_flex().child(
Label::new("Ask and build anything.").color(Color::Muted),
))
.child(
h_flex().child(
Label::new("Ask and build anything.")
.color(Color::Muted)
.mb_2p5(),
),
)
.child(
Button::new("new-thread", "Start New Thread")
.icon(IconName::Plus)
.icon_position(IconPosition::Start)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.full_width()
.key_binding(KeyBinding::for_action_in(
&NewThread::default(),
&focus_handle,
window,
cx,
))
.on_click(|_event, window, cx| {
window.dispatch_action(
NewThread::default().boxed_clone(),
cx,
)
}),
)
.child(
Button::new("context", "Add Context")
.icon(IconName::FileCode)
.icon_position(IconPosition::Start)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.full_width()
.key_binding(KeyBinding::for_action_in(
&ToggleContextPicker,
&focus_handle,
window,
cx,
))
.on_click(|_event, window, cx| {
window.dispatch_action(
ToggleContextPicker.boxed_clone(),
cx,
)
}),
)
.child(
Button::new("mode", "Switch Model")
.icon(IconName::DatabaseZap)
.icon_position(IconPosition::Start)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.full_width()
.key_binding(KeyBinding::for_action_in(
&ToggleModelSelector,
&focus_handle,
window,
cx,
))
.on_click(|_event, window, cx| {
window.dispatch_action(
ToggleModelSelector.boxed_clone(),
cx,
)
}),
)
.child(
Button::new("settings", "View Settings")
.icon(IconName::Settings)
.icon_position(IconPosition::Start)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.full_width()
.key_binding(KeyBinding::for_action_in(
&OpenConfiguration,
&focus_handle,
window,
cx,
))
.on_click(|_event, window, cx| {
window.dispatch_action(
OpenConfiguration.boxed_clone(),
cx,
)
}),
v_flex()
.mt_2()
.gap_1()
.max_w_48()
.child(
Button::new("context", "Add Context")
.label_size(LabelSize::Small)
.icon(IconName::FileCode)
.icon_position(IconPosition::Start)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.full_width()
.key_binding(KeyBinding::for_action_in(
&ToggleContextPicker,
&focus_handle,
window,
cx,
))
.on_click(|_event, window, cx| {
window.dispatch_action(
ToggleContextPicker.boxed_clone(),
cx,
)
}),
)
.child(
Button::new("mode", "Switch Model")
.label_size(LabelSize::Small)
.icon(IconName::DatabaseZap)
.icon_position(IconPosition::Start)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.full_width()
.key_binding(KeyBinding::for_action_in(
&ToggleModelSelector,
&focus_handle,
window,
cx,
))
.on_click(|_event, window, cx| {
window.dispatch_action(
ToggleModelSelector.boxed_clone(),
cx,
)
}),
)
.child(
Button::new("settings", "View Settings")
.label_size(LabelSize::Small)
.icon(IconName::Settings)
.icon_position(IconPosition::Start)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.full_width()
.key_binding(KeyBinding::for_action_in(
&OpenConfiguration,
&focus_handle,
window,
cx,
))
.on_click(|_event, window, cx| {
window.dispatch_action(
OpenConfiguration.boxed_clone(),
cx,
)
}),
),
)
})
.map(|parent| match configuration_error_ref {
Some(
err @ (ConfigurationError::ModelNotFound
| ConfigurationError::ProviderNotAuthenticated(_)
| ConfigurationError::NoProvider),
) => parent
.child(h_flex().child(
Label::new(err.to_string()).color(Color::Muted).mb_2p5(),
))
.child(
Button::new("settings", "Configure a Provider")
.icon(IconName::Settings)
.icon_position(IconPosition::Start)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.full_width()
.key_binding(KeyBinding::for_action_in(
&OpenConfiguration,
&focus_handle,
window,
cx,
))
.on_click(|_event, window, cx| {
window.dispatch_action(
OpenConfiguration.boxed_clone(),
cx,
)
}),
),
Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
parent.children(provider.render_accept_terms(
LanguageModelProviderTosView::ThreadFreshStart,
cx,
))
}
None => parent,
.when_some(configuration_error.as_ref(), |this, err| {
this.child(self.render_configuration_error(
err,
&focus_handle,
window,
cx,
))
}),
)
})
.when(!recent_history.is_empty(), |parent| {
let focus_handle = focus_handle.clone();
let configuration_error_ref = &configuration_error;
parent
.overflow_hidden()
.p_1p5()
@ -2735,49 +2460,55 @@ impl AgentPanel {
},
)),
)
.map(|parent| match configuration_error_ref {
Some(
err @ (ConfigurationError::ModelNotFound
| ConfigurationError::ProviderNotAuthenticated(_)
| ConfigurationError::NoProvider),
) => parent.child(
Banner::new()
.severity(ui::Severity::Warning)
.child(Label::new(err.to_string()).size(LabelSize::Small))
.action_slot(
Button::new("settings", "Configure Provider")
.style(ButtonStyle::Tinted(ui::TintColor::Warning))
.label_size(LabelSize::Small)
.key_binding(
KeyBinding::for_action_in(
&OpenConfiguration,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(|_event, window, cx| {
window.dispatch_action(
OpenConfiguration.boxed_clone(),
cx,
)
}),
),
),
Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
parent.child(Banner::new().severity(ui::Severity::Warning).child(
h_flex().w_full().children(provider.render_accept_terms(
LanguageModelProviderTosView::ThreadEmptyState,
cx,
)),
))
}
None => parent,
.when_some(configuration_error.as_ref(), |this, err| {
this.child(self.render_configuration_error(err, &focus_handle, window, cx))
})
})
}
fn render_configuration_error(
&self,
configuration_error: &ConfigurationError,
focus_handle: &FocusHandle,
window: &mut Window,
cx: &mut App,
) -> impl IntoElement {
match configuration_error {
ConfigurationError::ModelNotFound
| ConfigurationError::ProviderNotAuthenticated(_)
| ConfigurationError::NoProvider => Banner::new()
.severity(ui::Severity::Warning)
.child(Label::new(configuration_error.to_string()))
.action_slot(
Button::new("settings", "Configure Provider")
.style(ButtonStyle::Tinted(ui::TintColor::Warning))
.label_size(LabelSize::Small)
.key_binding(
KeyBinding::for_action_in(
&OpenConfiguration,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(|_event, window, cx| {
window.dispatch_action(OpenConfiguration.boxed_clone(), cx)
}),
),
ConfigurationError::ProviderPendingTermsAcceptance(provider) => {
Banner::new().severity(ui::Severity::Warning).child(
h_flex().w_full().children(
provider.render_accept_terms(
LanguageModelProviderTosView::ThreadEmptyState,
cx,
),
),
)
}
}
}
fn render_tool_use_limit_reached(
&self,
window: &mut Window,
@ -2910,7 +2641,7 @@ impl AgentPanel {
this.clear_last_error();
});
cx.open_url(&zed_urls::account_url(cx));
cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx));
cx.notify();
}
}))
@ -3300,7 +3031,7 @@ impl Render for AgentPanel {
}))
.on_action(cx.listener(Self::toggle_burn_mode))
.child(self.render_toolbar(window, cx))
.children(self.render_upsell(window, cx))
.children(self.render_onboarding(window, cx))
.children(self.render_trial_end_upsell(window, cx))
.map(|parent| match &self.active_view {
ActiveView::Thread {
@ -3309,12 +3040,14 @@ impl Render for AgentPanel {
..
} => parent
.relative()
.child(if thread.read(cx).is_empty() {
self.render_thread_empty_state(window, cx)
.into_any_element()
} else {
thread.clone().into_any_element()
})
.child(
if thread.read(cx).is_empty() && !self.should_render_onboarding(cx) {
self.render_thread_empty_state(window, cx)
.into_any_element()
} else {
thread.clone().into_any_element()
},
)
.children(self.render_tool_use_limit_reached(window, cx))
.when_some(thread.read(cx).last_error(), |this, last_error| {
this.child(
@ -3352,12 +3085,36 @@ impl Render for AgentPanel {
context_editor,
buffer_search_bar,
..
} => parent.child(self.render_prompt_editor(
context_editor,
buffer_search_bar,
window,
cx,
)),
} => {
let model_registry = LanguageModelRegistry::read_global(cx);
let configuration_error =
model_registry.configuration_error(model_registry.default_model(), cx);
parent
.map(|this| {
if !self.should_render_onboarding(cx)
&& let Some(err) = configuration_error.as_ref()
{
this.child(
div().bg(cx.theme().colors().editor_background).p_2().child(
self.render_configuration_error(
err,
&self.focus_handle(cx),
window,
cx,
),
),
)
} else {
this
}
})
.child(self.render_prompt_editor(
context_editor,
buffer_search_bar,
window,
cx,
))
}
ActiveView::Configuration => parent.children(self.configuration.clone()),
});
@ -3526,9 +3283,9 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
}
}
struct Upsell;
struct OnboardingUpsell;
impl Dismissable for Upsell {
impl Dismissable for OnboardingUpsell {
const KEY: &'static str = "dismissed-trial-upsell";
}