first pass of merging in warp (doesn't build)
This commit is contained in:
+316
-81
@@ -1,11 +1,12 @@
|
||||
use ai::LLMId;
|
||||
use galaxy_core::send_telemetry_from_ctx;
|
||||
use galaxyui_core::{Entity, ModelContext};
|
||||
|
||||
use crate::slides::{
|
||||
AgentAutonomy, AgentDevelopmentSettings, OnboardingModelInfo, ProjectOnboardingSettings,
|
||||
};
|
||||
use crate::telemetry::OnboardingEvent;
|
||||
use crate::OnboardingIntention;
|
||||
use ai::LLMId;
|
||||
use galaxy_core::send_telemetry_from_ctx;
|
||||
use galaxyui::{Entity, ModelContext};
|
||||
|
||||
/// UI customization settings chosen during the "Customize your UI" onboarding slide.
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -80,9 +81,12 @@ impl SelectedSettings {
|
||||
pub fn is_ai_enabled(&self) -> bool {
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
match self {
|
||||
SelectedSettings::AgentDrivenDevelopment { agent_settings, .. } => {
|
||||
!agent_settings.disable_oz
|
||||
}
|
||||
// Agent-driven development always means "I want AI" (including the
|
||||
// bring-your-own-agents `disable_oz` path). This reflects intent and
|
||||
// is used to decide that an account/login is required; whether AI is
|
||||
// actually enabled is applied later based on whether the user has an
|
||||
// account (see `apply_onboarding_settings`).
|
||||
SelectedSettings::AgentDrivenDevelopment { .. } => true,
|
||||
SelectedSettings::Terminal { .. } => {
|
||||
// With old onboarding (no OpenWarpNewSettingsModes), Terminal
|
||||
// intent still leaves AI enabled; with new onboarding,
|
||||
@@ -114,13 +118,57 @@ impl SelectedSettings {
|
||||
pub(crate) enum OnboardingStep {
|
||||
Intro,
|
||||
Intention,
|
||||
AiSetup,
|
||||
Customize,
|
||||
Agent,
|
||||
AiAccess,
|
||||
ThirdParty,
|
||||
Project,
|
||||
ThemePicker,
|
||||
}
|
||||
|
||||
/// The AI setup selected on the "Choose your AI setup" slide.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum AiSetupChoice {
|
||||
#[default]
|
||||
WarpAgent,
|
||||
ThirdParty,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AiSetupChoice {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
AiSetupChoice::WarpAgent => write!(f, "warp_agent"),
|
||||
AiSetupChoice::ThirdParty => write!(f, "third_party"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The access method selected on the "Choose how to access AI" slide (Warp Agent path).
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum AiAccessChoice {
|
||||
#[default]
|
||||
Subscription,
|
||||
SetUpLater,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AiAccessChoice {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
AiAccessChoice::Subscription => write!(f, "subscription"),
|
||||
AiAccessChoice::SetUpLater => write!(f, "set_up_later"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Which opt-out entry point opened the "Are you sure you don't want AI?" modal.
|
||||
/// Determines where "Give me AI features" routes the user on cancel.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum NoAiConfirmationSource {
|
||||
/// Triggered from the intention slide via "Just use the terminal" + Next.
|
||||
Intention,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum OnboardingStateEvent {
|
||||
ModelsUpdated,
|
||||
@@ -129,6 +177,7 @@ pub(crate) enum OnboardingStateEvent {
|
||||
Completed,
|
||||
UpgradeRequested,
|
||||
AuthStateChanged,
|
||||
NoAiConfirmationChanged,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -143,15 +192,15 @@ pub(crate) struct OnboardingStateModel {
|
||||
workspace_enforces_autonomy: bool,
|
||||
/// Whether the AgentView feature flag is enabled.
|
||||
agent_modality_enabled: bool,
|
||||
/// Whether the user is in the FreeUserNoAi experiment group (and is free tier).
|
||||
/// When true, the Agent Driven Development option on the intention slide is locked
|
||||
/// behind an upgrade CTA.
|
||||
free_user_no_ai_experiment: bool,
|
||||
/// Yearly price per month in USD cents for the agent plan badge.
|
||||
/// When `None`, falls back to a hardcoded default ($18/mo).
|
||||
agent_price_cents: Option<i32>,
|
||||
/// The AI setup selected on the "Choose your AI setup" slide.
|
||||
ai_setup_choice: AiSetupChoice,
|
||||
/// The access method selected on the "Choose how to access AI" slide.
|
||||
ai_access_choice: AiAccessChoice,
|
||||
/// Auth / billing state of the user.
|
||||
auth_state: OnboardingAuthState,
|
||||
/// When set, the "Are you sure you don't want AI?" confirmation modal is
|
||||
/// shown; the value records which entry point triggered it.
|
||||
no_ai_confirmation: Option<NoAiConfirmationSource>,
|
||||
}
|
||||
|
||||
impl OnboardingStateModel {
|
||||
@@ -161,8 +210,6 @@ impl OnboardingStateModel {
|
||||
default_model_id: LLMId,
|
||||
workspace_enforces_autonomy: bool,
|
||||
agent_modality_enabled: bool,
|
||||
free_user_no_ai_experiment: bool,
|
||||
agent_price_cents: Option<i32>,
|
||||
auth_state: OnboardingAuthState,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -174,9 +221,10 @@ impl OnboardingStateModel {
|
||||
models,
|
||||
workspace_enforces_autonomy,
|
||||
agent_modality_enabled,
|
||||
free_user_no_ai_experiment,
|
||||
agent_price_cents,
|
||||
ai_setup_choice: AiSetupChoice::default(),
|
||||
ai_access_choice: AiAccessChoice::default(),
|
||||
auth_state,
|
||||
no_ai_confirmation: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +245,6 @@ impl OnboardingStateModel {
|
||||
}
|
||||
|
||||
pub(crate) fn settings(&self) -> SelectedSettings {
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
let ui_customization = if FeatureFlag::OpenWarpNewSettingsModes.is_enabled() {
|
||||
Some(self.ui_customization.clone())
|
||||
} else {
|
||||
@@ -256,6 +303,111 @@ impl OnboardingStateModel {
|
||||
self.agent_modality_enabled
|
||||
}
|
||||
|
||||
/// Whether the DES-816 V3 onboarding flow (the "Choose your AI setup" fork on the
|
||||
/// AI-first path) is active. True for all users when the new settings-modes flow
|
||||
/// is enabled, since new users always enter a world where Warp-provided AI is not free.
|
||||
pub(crate) fn ai_setup_flow_active(&self) -> bool {
|
||||
FeatureFlag::OpenWarpNewSettingsModes.is_enabled()
|
||||
}
|
||||
|
||||
pub(crate) fn ai_setup_choice(&self) -> AiSetupChoice {
|
||||
self.ai_setup_choice
|
||||
}
|
||||
|
||||
pub(crate) fn ai_access_choice(&self) -> AiAccessChoice {
|
||||
self.ai_access_choice
|
||||
}
|
||||
|
||||
pub(crate) fn set_ai_setup_choice(
|
||||
&mut self,
|
||||
choice: AiSetupChoice,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if self.ai_setup_choice == choice {
|
||||
return;
|
||||
}
|
||||
send_telemetry_from_ctx!(
|
||||
OnboardingEvent::SettingChanged {
|
||||
setting: "ai_setup".to_string(),
|
||||
value: choice.to_string(),
|
||||
},
|
||||
ctx
|
||||
);
|
||||
self.ai_setup_choice = choice;
|
||||
self.agent_settings.disable_oz = matches!(choice, AiSetupChoice::ThirdParty);
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
pub(crate) fn set_ai_access_choice(
|
||||
&mut self,
|
||||
choice: AiAccessChoice,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if self.ai_access_choice == choice {
|
||||
return;
|
||||
}
|
||||
send_telemetry_from_ctx!(
|
||||
OnboardingEvent::SettingChanged {
|
||||
setting: "ai_access".to_string(),
|
||||
value: choice.to_string(),
|
||||
},
|
||||
ctx
|
||||
);
|
||||
self.ai_access_choice = choice;
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
pub(crate) fn no_ai_confirmation(&self) -> Option<NoAiConfirmationSource> {
|
||||
self.no_ai_confirmation
|
||||
}
|
||||
|
||||
/// Shows the "Are you sure you don't want AI?" confirmation modal, recording
|
||||
/// which opt-out entry point triggered it so cancel can route appropriately.
|
||||
pub(crate) fn request_no_ai_confirmation(
|
||||
&mut self,
|
||||
source: NoAiConfirmationSource,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
send_telemetry_from_ctx!(OnboardingEvent::NoAiConfirmationShown, ctx);
|
||||
self.no_ai_confirmation = Some(source);
|
||||
ctx.emit(OnboardingStateEvent::NoAiConfirmationChanged);
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
/// "I don't want AI": commit to the terminal-only path (AI features off) and
|
||||
/// continue the flow there, so declining AI never dead-ends onboarding.
|
||||
pub(crate) fn confirm_no_ai(&mut self, ctx: &mut ModelContext<Self>) {
|
||||
send_telemetry_from_ctx!(OnboardingEvent::NoAiConfirmed, ctx);
|
||||
self.no_ai_confirmation = None;
|
||||
self.set_intention(OnboardingIntention::Terminal, ctx);
|
||||
self.set_step(OnboardingStep::Customize, ctx);
|
||||
}
|
||||
|
||||
/// "Give me AI features": abort the opt-out. The only trigger is the
|
||||
/// intention slide's "Just use the terminal", which is an explicit request
|
||||
/// for AI, so route onto the AI path.
|
||||
pub(crate) fn cancel_no_ai(&mut self, ctx: &mut ModelContext<Self>) {
|
||||
send_telemetry_from_ctx!(OnboardingEvent::NoAiConfirmationCancelled, ctx);
|
||||
match self.no_ai_confirmation.take() {
|
||||
Some(NoAiConfirmationSource::Intention) => {
|
||||
self.set_intention(OnboardingIntention::AgentDrivenDevelopment, ctx);
|
||||
self.set_step(OnboardingStep::AiSetup, ctx);
|
||||
}
|
||||
None => {
|
||||
ctx.emit(OnboardingStateEvent::NoAiConfirmationChanged);
|
||||
ctx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes the confirmation modal without changing the user's path (ESC / X).
|
||||
pub(crate) fn dismiss_no_ai(&mut self, ctx: &mut ModelContext<Self>) {
|
||||
if self.no_ai_confirmation.take().is_some() {
|
||||
ctx.emit(OnboardingStateEvent::NoAiConfirmationChanged);
|
||||
ctx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ui_customization(&self) -> &UICustomizationSettings {
|
||||
&self.ui_customization
|
||||
}
|
||||
@@ -290,28 +442,6 @@ impl OnboardingStateModel {
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
pub(crate) fn free_user_no_ai_experiment(&self) -> bool {
|
||||
self.free_user_no_ai_experiment
|
||||
}
|
||||
|
||||
pub(crate) fn agent_price_badge(&self) -> String {
|
||||
const DEFAULT_AGENT_PRICE_CENTS: i32 = 1800;
|
||||
let cents = self.agent_price_cents.unwrap_or(DEFAULT_AGENT_PRICE_CENTS);
|
||||
format!("Starting at ${}/mo", cents / 100)
|
||||
}
|
||||
|
||||
pub(crate) fn set_agent_price_cents(
|
||||
&mut self,
|
||||
cents: Option<i32>,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if self.agent_price_cents == cents {
|
||||
return;
|
||||
}
|
||||
self.agent_price_cents = cents;
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
pub(crate) fn set_show_conversation_history(
|
||||
&mut self,
|
||||
value: bool,
|
||||
@@ -448,18 +578,6 @@ impl OnboardingStateModel {
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
pub(crate) fn set_free_user_no_ai_experiment(
|
||||
&mut self,
|
||||
value: bool,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if self.free_user_no_ai_experiment == value {
|
||||
return;
|
||||
}
|
||||
self.free_user_no_ai_experiment = value;
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
pub(crate) fn set_workspace_enforces_autonomy(
|
||||
&mut self,
|
||||
value: bool,
|
||||
@@ -512,13 +630,6 @@ impl OnboardingStateModel {
|
||||
self.set_intention(OnboardingIntention::AgentDrivenDevelopment, ctx);
|
||||
}
|
||||
|
||||
pub(crate) fn is_model_disabled(&self, model_id: &LLMId) -> bool {
|
||||
self.models
|
||||
.iter()
|
||||
.find(|m| &m.id == model_id)
|
||||
.is_some_and(|m| m.requires_upgrade)
|
||||
}
|
||||
|
||||
pub(crate) fn request_upgrade(&mut self, ctx: &mut ModelContext<Self>) {
|
||||
ctx.emit(OnboardingStateEvent::UpgradeRequested);
|
||||
}
|
||||
@@ -528,10 +639,6 @@ impl OnboardingStateModel {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.is_model_disabled(&model_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
send_telemetry_from_ctx!(
|
||||
OnboardingEvent::SettingChanged {
|
||||
setting: "model".to_string(),
|
||||
@@ -551,7 +658,6 @@ impl OnboardingStateModel {
|
||||
default_model_id: LLMId,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
|
||||
// If the user is past the agent slide, don't change the agent model from underneath them.
|
||||
// When the new settings modes flag is on, ThemePicker comes after the agent slides
|
||||
@@ -631,12 +737,13 @@ impl OnboardingStateModel {
|
||||
}
|
||||
|
||||
fn send_completion_telemetry(&self, ctx: &mut ModelContext<Self>) {
|
||||
let (intention, model, autonomy) = match &self.intention {
|
||||
OnboardingIntention::Terminal => (self.intention.to_string(), None, None),
|
||||
let (intention, model, autonomy, ai_access) = match &self.intention {
|
||||
OnboardingIntention::Terminal => (self.intention.to_string(), None, None, None),
|
||||
OnboardingIntention::AgentDrivenDevelopment => (
|
||||
self.intention.to_string(),
|
||||
Some(self.agent_settings.selected_model_id.to_string()),
|
||||
self.agent_settings.autonomy.map(|x| x.to_string()),
|
||||
Some(self.ai_setup_choice.to_string()),
|
||||
),
|
||||
};
|
||||
|
||||
@@ -651,6 +758,7 @@ impl OnboardingStateModel {
|
||||
model,
|
||||
autonomy,
|
||||
has_project_path,
|
||||
ai_access,
|
||||
},
|
||||
ctx
|
||||
);
|
||||
@@ -663,27 +771,45 @@ impl OnboardingStateModel {
|
||||
}
|
||||
|
||||
pub(crate) fn back(&mut self, ctx: &mut ModelContext<Self>) {
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
let theme_picker_last = FeatureFlag::OpenWarpNewSettingsModes.is_enabled();
|
||||
let ai_setup_flow = self.ai_setup_flow_active();
|
||||
let agent_intention = matches!(self.intention, OnboardingIntention::AgentDrivenDevelopment);
|
||||
|
||||
let prev = if theme_picker_last {
|
||||
match self.step {
|
||||
OnboardingStep::Intro => None,
|
||||
OnboardingStep::Intention => Some(OnboardingStep::Intro),
|
||||
OnboardingStep::Customize => Some(OnboardingStep::Intention),
|
||||
OnboardingStep::Agent => Some(OnboardingStep::Customize),
|
||||
OnboardingStep::ThirdParty => match self.intention {
|
||||
OnboardingIntention::Terminal => Some(OnboardingStep::Customize),
|
||||
OnboardingIntention::AgentDrivenDevelopment => Some(OnboardingStep::Agent),
|
||||
},
|
||||
OnboardingStep::AiSetup => Some(OnboardingStep::Intention),
|
||||
OnboardingStep::Customize => {
|
||||
if ai_setup_flow && agent_intention {
|
||||
match self.ai_setup_choice {
|
||||
AiSetupChoice::WarpAgent => Some(OnboardingStep::AiAccess),
|
||||
AiSetupChoice::ThirdParty => Some(OnboardingStep::ThirdParty),
|
||||
}
|
||||
} else {
|
||||
Some(OnboardingStep::Intention)
|
||||
}
|
||||
}
|
||||
OnboardingStep::AiAccess => Some(OnboardingStep::Agent),
|
||||
OnboardingStep::Agent => {
|
||||
if ai_setup_flow {
|
||||
Some(OnboardingStep::AiSetup)
|
||||
} else {
|
||||
Some(OnboardingStep::Customize)
|
||||
}
|
||||
}
|
||||
OnboardingStep::ThirdParty => Some(OnboardingStep::AiSetup),
|
||||
OnboardingStep::Project => Some(OnboardingStep::ThirdParty),
|
||||
OnboardingStep::ThemePicker => Some(OnboardingStep::ThirdParty),
|
||||
OnboardingStep::ThemePicker => Some(OnboardingStep::Customize),
|
||||
}
|
||||
} else {
|
||||
match self.step {
|
||||
OnboardingStep::Intro => None,
|
||||
OnboardingStep::ThemePicker => Some(OnboardingStep::Intro),
|
||||
OnboardingStep::Intention => Some(OnboardingStep::ThemePicker),
|
||||
// Unreachable in the legacy flow.
|
||||
OnboardingStep::AiSetup => None,
|
||||
OnboardingStep::AiAccess => None,
|
||||
OnboardingStep::Customize => None,
|
||||
OnboardingStep::ThirdParty => None,
|
||||
OnboardingStep::Agent => Some(OnboardingStep::Intention),
|
||||
@@ -698,7 +824,6 @@ impl OnboardingStateModel {
|
||||
}
|
||||
|
||||
pub(crate) fn next(&mut self, ctx: &mut ModelContext<Self>) {
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
let theme_picker_last = FeatureFlag::OpenWarpNewSettingsModes.is_enabled();
|
||||
|
||||
let is_last_step = if theme_picker_last {
|
||||
@@ -711,17 +836,52 @@ impl OnboardingStateModel {
|
||||
}
|
||||
|
||||
if theme_picker_last {
|
||||
let ai_setup_flow = self.ai_setup_flow_active();
|
||||
match self.step {
|
||||
OnboardingStep::Intro => self.set_step(OnboardingStep::Intention, ctx),
|
||||
OnboardingStep::Intention => self.set_step(OnboardingStep::Customize, ctx),
|
||||
OnboardingStep::Customize => match self.intention {
|
||||
OnboardingIntention::Terminal => self.set_step(OnboardingStep::ThirdParty, ctx),
|
||||
OnboardingStep::Intention => match self.intention {
|
||||
OnboardingIntention::Terminal => self.set_step(OnboardingStep::Customize, ctx),
|
||||
OnboardingIntention::AgentDrivenDevelopment => {
|
||||
self.set_step(OnboardingStep::Agent, ctx)
|
||||
if ai_setup_flow {
|
||||
self.set_step(OnboardingStep::AiSetup, ctx)
|
||||
} else {
|
||||
self.set_step(OnboardingStep::Customize, ctx)
|
||||
}
|
||||
}
|
||||
},
|
||||
OnboardingStep::Agent => self.set_step(OnboardingStep::ThirdParty, ctx),
|
||||
OnboardingStep::ThirdParty => self.set_step(OnboardingStep::ThemePicker, ctx),
|
||||
OnboardingStep::AiSetup => match self.ai_setup_choice {
|
||||
AiSetupChoice::WarpAgent => self.set_step(OnboardingStep::Agent, ctx),
|
||||
AiSetupChoice::ThirdParty => self.set_step(OnboardingStep::ThirdParty, ctx),
|
||||
},
|
||||
OnboardingStep::Customize => match self.intention {
|
||||
OnboardingIntention::Terminal => {
|
||||
self.set_step(OnboardingStep::ThemePicker, ctx)
|
||||
}
|
||||
OnboardingIntention::AgentDrivenDevelopment => {
|
||||
if ai_setup_flow {
|
||||
self.set_step(OnboardingStep::ThemePicker, ctx)
|
||||
} else {
|
||||
self.set_step(OnboardingStep::Agent, ctx)
|
||||
}
|
||||
}
|
||||
},
|
||||
OnboardingStep::Agent => {
|
||||
if ai_setup_flow {
|
||||
self.set_step(OnboardingStep::AiAccess, ctx)
|
||||
} else {
|
||||
self.set_step(OnboardingStep::ThirdParty, ctx)
|
||||
}
|
||||
}
|
||||
OnboardingStep::AiAccess => self.set_step(OnboardingStep::Customize, ctx),
|
||||
OnboardingStep::ThirdParty => {
|
||||
if ai_setup_flow
|
||||
&& matches!(self.intention, OnboardingIntention::AgentDrivenDevelopment)
|
||||
{
|
||||
self.set_step(OnboardingStep::Customize, ctx)
|
||||
} else {
|
||||
self.set_step(OnboardingStep::ThemePicker, ctx)
|
||||
}
|
||||
}
|
||||
OnboardingStep::Project => self.set_step(OnboardingStep::ThemePicker, ctx),
|
||||
OnboardingStep::ThemePicker => {}
|
||||
}
|
||||
@@ -730,6 +890,9 @@ impl OnboardingStateModel {
|
||||
OnboardingStep::Intro => self.set_step(OnboardingStep::ThemePicker, ctx),
|
||||
OnboardingStep::ThemePicker => self.set_step(OnboardingStep::Intention, ctx),
|
||||
OnboardingStep::Intention => self.set_step(OnboardingStep::Agent, ctx),
|
||||
// Unreachable in the legacy flow.
|
||||
OnboardingStep::AiSetup => {}
|
||||
OnboardingStep::AiAccess => {}
|
||||
OnboardingStep::Customize => {}
|
||||
OnboardingStep::ThirdParty => {}
|
||||
OnboardingStep::Agent => self.set_step(OnboardingStep::Project, ctx),
|
||||
@@ -770,6 +933,22 @@ impl OnboardingStateModel {
|
||||
ctx
|
||||
);
|
||||
}
|
||||
OnboardingStep::AiSetup => {
|
||||
send_telemetry_from_ctx!(
|
||||
OnboardingEvent::SlideViewed {
|
||||
slide_name: "ai_setup".to_string(),
|
||||
},
|
||||
ctx
|
||||
);
|
||||
}
|
||||
OnboardingStep::AiAccess => {
|
||||
send_telemetry_from_ctx!(
|
||||
OnboardingEvent::SlideViewed {
|
||||
slide_name: "ai_access".to_string(),
|
||||
},
|
||||
ctx
|
||||
);
|
||||
}
|
||||
OnboardingStep::Customize => {
|
||||
send_telemetry_from_ctx!(
|
||||
OnboardingEvent::SlideViewed {
|
||||
@@ -807,8 +986,64 @@ impl OnboardingStateModel {
|
||||
ctx.emit(OnboardingStateEvent::SelectedSlideChanged);
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
/// The `(step_index, step_count)` shown by the bottom-nav progress dots for the
|
||||
/// current step, intention, and flow variant.
|
||||
pub(crate) fn progress(&self) -> (usize, usize) {
|
||||
|
||||
let is_terminal = matches!(self.intention, OnboardingIntention::Terminal);
|
||||
if !FeatureFlag::OpenWarpNewSettingsModes.is_enabled() {
|
||||
// Legacy flow: ThemePicker → Intention → Agent → Project.
|
||||
return match self.step {
|
||||
OnboardingStep::Intro | OnboardingStep::ThemePicker => (0, 4),
|
||||
OnboardingStep::Intention | OnboardingStep::AiSetup | OnboardingStep::Customize => {
|
||||
(1, 4)
|
||||
}
|
||||
OnboardingStep::Agent | OnboardingStep::ThirdParty | OnboardingStep::AiAccess => {
|
||||
(2, 4)
|
||||
}
|
||||
OnboardingStep::Project => (3, 4),
|
||||
};
|
||||
}
|
||||
|
||||
// The Warp Agent path has the extra "Choose how to access AI" step, so it
|
||||
// is one longer than the third-party-agent path.
|
||||
let is_warp_agent_path =
|
||||
!is_terminal && matches!(self.ai_setup_choice, AiSetupChoice::WarpAgent);
|
||||
let step_count = if is_terminal {
|
||||
3
|
||||
} else if is_warp_agent_path {
|
||||
6
|
||||
} else {
|
||||
5
|
||||
};
|
||||
let step_index = match self.step {
|
||||
OnboardingStep::Intro | OnboardingStep::Intention => 0,
|
||||
OnboardingStep::AiSetup => 1,
|
||||
OnboardingStep::Agent => 2,
|
||||
OnboardingStep::AiAccess => 3,
|
||||
OnboardingStep::Customize => {
|
||||
if is_terminal {
|
||||
1
|
||||
} else if is_warp_agent_path {
|
||||
4
|
||||
} else {
|
||||
3
|
||||
}
|
||||
}
|
||||
OnboardingStep::ThirdParty => 2,
|
||||
// Unreachable in the new flow; keep the legacy position.
|
||||
OnboardingStep::Project => 3,
|
||||
OnboardingStep::ThemePicker => step_count - 1,
|
||||
};
|
||||
(step_index, step_count)
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for OnboardingStateModel {
|
||||
type Event = OnboardingStateEvent;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "model_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
Reference in New Issue
Block a user