From af520193a98be81447a72e31c530c79e987ff0fd Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Thu, 6 Aug 2026 16:40:48 -0500 Subject: [PATCH] Remove account billing UI from AI settings --- app/src/settings_view/ai_page.rs | 340 ------------------------------- plans/galaxy-local-first-rig.md | 2 + 2 files changed, 2 insertions(+), 340 deletions(-) diff --git a/app/src/settings_view/ai_page.rs b/app/src/settings_view/ai_page.rs index 8dc00ada..050e3187 100644 --- a/app/src/settings_view/ai_page.rs +++ b/app/src/settings_view/ai_page.rs @@ -74,9 +74,6 @@ use crate::ai::llms::{ }; use crate::ai::mcp::TemplatableMCPServerManager; use crate::ai::paths::host_native_absolute_path; -use crate::auth::auth_manager::{AuthManager, LoginGatedFeature}; -use crate::auth::auth_view_modal::AuthViewVariant; -use crate::auth::AuthStateProvider; use crate::cloud_object::model::persistence::{CloudModel, CloudModelEvent}; use crate::cloud_object::GenericStringObjectFormat::Json; use crate::cloud_object::{JsonObjectType, ObjectType}; @@ -151,7 +148,6 @@ use std::sync::LazyLock; use markdown_parser::{FormattedText, FormattedTextFragment, FormattedTextLine}; -use crate::ai::{AIRequestUsageModel, AIRequestUsageModelEvent}; use crate::appearance::{Appearance, AppearanceEvent}; use crate::editor::{EditorView, Event as EditorEvent, TextOptions}; use crate::menu::{MenuItem, MenuItemFields}; @@ -1142,11 +1138,6 @@ impl AISettingsPageView { }, ); - let request_usage_model = AIRequestUsageModel::handle(ctx); - ctx.subscribe_to_model(&request_usage_model, |_, _, _, ctx| { - ctx.notify(); - }); - ctx.subscribe_to_model(&UserWorkspaces::handle(ctx), |me, _handle, _event, ctx| { // Re-render if teams-related data changed that may affect whether features such as voice input are enabled. Self::refresh_base_model_menu(&me.base_model_dropdown, ctx); @@ -1742,17 +1733,6 @@ impl AISettingsPageView { } }); - let ai_request_model = AIRequestUsageModel::handle(ctx); - ctx.subscribe_to_model(&ai_request_model, |me, _, event, ctx| { - match event { - AIRequestUsageModelEvent::RequestUsageUpdated => ctx.notify(), - AIRequestUsageModelEvent::RequestBonusRefunded { .. } => ctx.notify(), - AIRequestUsageModelEvent::AmbientCreditsBannerDismissed => {} - } - Self::refresh_base_model_menu(&me.base_model_dropdown, ctx); - Self::refresh_coding_model_menu(&me.coding_model_dropdown, ctx); - }); - let profile_views = Self::create_profile_views(ctx); // Custom model router views @@ -2159,9 +2139,6 @@ impl AISettingsPageView { None => { // Full page: all widgets (legacy behavior) widgets.push(Box::new(GlobalAIWidget::default())); - if !FeatureFlag::UsageBasedPricing.is_enabled() { - widgets.push(Box::new(UsageWidget::default())); - } if ai_settings .intelligent_autosuggestions_enabled_internal .is_supported_on_current_platform() @@ -2243,9 +2220,6 @@ impl AISettingsPageView { widgets.push(Box::new(OtherAIWidget::default())); } Some(AISubpage::Profiles) => { - if !FeatureFlag::UsageBasedPricing.is_enabled() { - widgets.push(Box::new(UsageWidget::default())); - } widgets.push(Box::new(AgentsWidget::default())); } Some(AISubpage::Knowledge) => { @@ -2981,7 +2955,6 @@ pub enum AISettingsPageAction { SetOrchestrationMessageDisplayMode(OrchestrationMessageDisplayMode), SetPromptSubmissionMode(PromptSubmissionMode), SetLongRunningCommandSubmissionMode(LongRunningCommandSubmissionMode), - AttemptLoginGatedUpgrade, RemoveCLIAgentToolbarEnabledCommand(String), RemoveFromCommandExecutionAllowlist(AgentModeCommandExecutionPredicate), RemoveFromCommandExecutionDenylist(AgentModeCommandExecutionPredicate), @@ -3057,16 +3030,6 @@ pub enum AISettingsPageAction { ToggleCrosscheckEnabled, } -impl From<&AISettingsPageAction> for LoginGatedFeature { - fn from(val: &AISettingsPageAction) -> LoginGatedFeature { - use AISettingsPageAction::*; - match val { - AttemptLoginGatedUpgrade => "Upgrade AI Usage", - _ => "Unknown reason", - } - } -} - impl TypedActionView for AISettingsPageView { type Action = AISettingsPageAction; @@ -3459,15 +3422,6 @@ impl TypedActionView for AISettingsPageView { }); ctx.notify(); } - AISettingsPageAction::AttemptLoginGatedUpgrade => { - AuthManager::handle(ctx).update(ctx, |auth_manager, ctx| { - auth_manager.attempt_login_gated_feature( - action.into(), - AuthViewVariant::RequireLoginCloseable, - ctx, - ) - }); - } AISettingsPageAction::RemoveCLIAgentToolbarEnabledCommand(command) => { AISettings::handle(ctx).update(ctx, |settings, ctx| { settings.remove_cli_agent_footer_enabled_command(command, ctx); @@ -3976,12 +3930,6 @@ impl SettingsPageMeta for AISettingsPageView { FeatureFlag::AgentMode.is_enabled() } - fn on_page_selected(&mut self, _: bool, ctx: &mut ViewContext) { - AIRequestUsageModel::handle(ctx).update(ctx, |ai_request_usage_model, ctx| { - ai_request_usage_model.refresh_request_usage_async(ctx) - }); - } - fn update_filter(&mut self, query: &str, ctx: &mut ViewContext) -> MatchData { self.page.update_filter(query, ctx) } @@ -4255,294 +4203,6 @@ impl SettingsWidget for GlobalAIWidget { } } -#[derive(Default)] -struct UsageWidget { - requests_highlight_index: HighlightedHyperlink, -} - -impl UsageWidget { - fn render_request_usage_count( - &self, - used: usize, - limit: usize, - is_unlimited: bool, - workspace_is_delinquent_due_to_payment_issue: bool, - appearance: &Appearance, - ) -> Box { - let mut row = Flex::row(); - if used >= limit || workspace_is_delinquent_due_to_payment_issue { - row.add_child( - ConstrainedBox::new( - Icon::AlertTriangle - .to_galaxyui_icon(appearance.theme().ui_error_color().into()) - .finish(), - ) - .with_height(16.) - .with_width(16.) - .finish(), - ) - } - - let request_count_label = if workspace_is_delinquent_due_to_payment_issue { - "Restricted due to billing issue".to_string() - } else if is_unlimited { - "Unlimited".to_string() - } else { - format!("{used}/{limit}") - }; - - row.add_child( - appearance - .ui_builder() - .paragraph(request_count_label) - .with_style(UiComponentStyles { - font_color: { - if used >= limit { - Some(appearance.theme().ui_error_color()) - } else { - Some(blended_colors::text_sub( - appearance.theme(), - appearance.theme().surface_1(), - )) - } - }, - font_size: Some(16.), - margin: Some(Coords { - top: 0., - bottom: 0., - left: 8., - right: 0., - }), - ..Default::default() - }) - .build() - .finish(), - ); - - row.finish() - } - - /// Renders a row of what is being limited, along with the current used/limit. - #[allow(clippy::too_many_arguments)] - fn render_ai_usage_limit_row( - &self, - header: impl Into>, - description: impl Into>, - used: usize, - limit: usize, - is_unlimited: bool, - workspace_is_delinquent_due_to_payment_issue: bool, - appearance: &Appearance, - ) -> Box { - let request_usage_details = Flex::column() - .with_cross_axis_alignment(CrossAxisAlignment::End) - .with_child(self.render_request_usage_count( - used, - limit, - is_unlimited, - workspace_is_delinquent_due_to_payment_issue, - appearance, - )); - - let request_usage_description = FormattedTextElement::from_str( - description, - appearance.ui_font_family(), - appearance.ui_font_size(), - ) - .with_color(blended_colors::text_sub( - appearance.theme(), - appearance.theme().surface_1(), - )); - - Flex::row() - .with_child( - Shrinkable::new( - 2., - Container::new( - Flex::column() - .with_child( - appearance - .ui_builder() - .paragraph(header) - .with_style(UiComponentStyles { - font_color: Some(blended_colors::text_main( - appearance.theme(), - appearance.theme().surface_1(), - )), - margin: Some(Coords { - top: 0., - bottom: 4., - left: 0., - right: 0., - }), - ..Default::default() - }) - .build() - .finish(), - ) - .with_child(request_usage_description.finish()) - .finish(), - ) - .with_margin_bottom(16.) - .finish(), - ) - .finish(), - ) - .with_child( - Shrinkable::new( - 1., - Container::new(request_usage_details.finish()) - .with_margin_bottom(16.) - .finish(), - ) - .finish(), - ) - .with_cross_axis_alignment(CrossAxisAlignment::Center) - .with_main_axis_alignment(MainAxisAlignment::SpaceBetween) - .with_main_axis_size(MainAxisSize::Max) - .finish() - } -} - -impl SettingsWidget for UsageWidget { - type View = AISettingsPageView; - - fn search_terms(&self) -> &str { - "a.i. ai usage limit plan" - } - - fn render( - &self, - _view: &Self::View, - appearance: &Appearance, - app: &AppContext, - ) -> Box { - let ai_request_usage_model = AIRequestUsageModel::as_ref(app); - let next_refresh_time = ai_request_usage_model.next_refresh_time(); - let formatted_next_refresh_time = next_refresh_time.format("%b %d").to_string(); - let workspace_is_delinquent_due_to_payment_issue = UserWorkspaces::as_ref(app) - .current_team() - .map(|team| team.billing_metadata.is_delinquent_due_to_payment_issue()) - .unwrap_or_default(); - - let usage_header = Container::new( - Flex::row() - .with_main_axis_size(MainAxisSize::Max) - .with_main_axis_alignment(MainAxisAlignment::SpaceBetween) - .with_cross_axis_alignment(CrossAxisAlignment::Center) - .with_child( - build_sub_header( - appearance, - "Usage", - Some(styles::header_font_color(true, app)), - ) - .finish(), - ) - .with_child( - appearance - .ui_builder() - .paragraph(format!("Resets {formatted_next_refresh_time}")) - .with_style(UiComponentStyles { - font_color: Some(blended_colors::text_sub( - appearance.theme(), - appearance.theme().surface_1(), - )), - ..Default::default() - }) - .build() - .finish(), - ) - .finish(), - ) - .with_padding_bottom(HEADER_PADDING) - .finish(); - - let request_limit_description = format!( - "This is the {} limit of AI credits for your account.", - ai_request_usage_model.refresh_duration_to_string() - ); - - let request_usage_row = self.render_ai_usage_limit_row( - "Credits", - request_limit_description, - ai_request_usage_model.requests_used(), - ai_request_usage_model.request_limit(), - ai_request_usage_model.is_unlimited(), - workspace_is_delinquent_due_to_payment_issue, - appearance, - ); - - let auth_state = AuthStateProvider::as_ref(app).get(); - let upgrade_cta_text_fragments = if let Some(team) = - UserWorkspaces::as_ref(app).current_team() - { - let current_user_email = auth_state.user_email().unwrap_or_default(); - let has_admin_permissions = team.has_admin_permissions(¤t_user_email); - if team.billing_metadata.can_upgrade_to_higher_tier_plan() { - let upgrade_url = UserWorkspaces::upgrade_link_for_team(team.uid); - if has_admin_permissions { - vec![ - FormattedTextFragment::hyperlink("Upgrade", upgrade_url), - FormattedTextFragment::plain_text(" to get more AI usage."), - ] - } else { - // The /upgrade page says to contact their administrator. - vec![ - FormattedTextFragment::hyperlink("Compare plans", upgrade_url), - FormattedTextFragment::plain_text(" for more AI usage."), - ] - } - } else { - vec![ - FormattedTextFragment::hyperlink("Contact support", "mailto:support@warp.dev"), - FormattedTextFragment::plain_text(" for more AI usage."), - ] - } - } else { - let user_id = auth_state.user_id().unwrap_or_default(); - let upgrade_url = UserWorkspaces::upgrade_link(user_id); - vec![ - FormattedTextFragment::hyperlink("Upgrade", upgrade_url), - FormattedTextFragment::plain_text(" to get more AI usage."), - ] - }; - - let mut upgrade_cta = FormattedTextElement::new( - FormattedText::new([FormattedTextLine::Line(upgrade_cta_text_fragments)]), - appearance.ui_font_size(), - appearance.ui_font_family(), - appearance.ui_font_family(), - styles::description_font_color(true, app).into(), - self.requests_highlight_index.clone(), - ) - .with_hyperlink_font_color(appearance.theme().accent().into_solid()); - - if AuthStateProvider::as_ref(app) - .get() - .is_anonymous_or_logged_out() - { - upgrade_cta = upgrade_cta.register_default_click_handlers(|_, ctx, _| { - ctx.dispatch_typed_action(AISettingsPageAction::AttemptLoginGatedUpgrade); - }); - } else { - upgrade_cta = upgrade_cta.register_default_click_handlers(|url, ctx, _| { - ctx.dispatch_typed_action(AISettingsPageAction::HyperlinkClick(url)); - }) - } - - Flex::column() - .with_children([ - render_separator(appearance), - usage_header, - request_usage_row, - Container::new(upgrade_cta.finish()) - .with_margin_bottom(16.) - .finish(), - ]) - .finish() - } -} - #[derive(Default)] struct ActiveAIWidget { active_ai_toggle: SwitchStateHandle, diff --git a/plans/galaxy-local-first-rig.md b/plans/galaxy-local-first-rig.md index 724f6f93..7fa29bdf 100644 --- a/plans/galaxy-local-first-rig.md +++ b/plans/galaxy-local-first-rig.md @@ -407,6 +407,8 @@ metadata. Drive remains usable while logged out; team-only actions retain their separate restrictions. - [x] Remove the stale account/signup gate from the Global Agent control so configured local providers and runtimes remain usable while logged out. +- [x] Remove the inherited account-credit, billing-status, and upgrade CTA widget from AI + settings; configured local providers own their credentials and usage limits. - [x] Move the retained Drive import flow onto local persistence for personal targets, including local folder/notebook/workflow creation and progress reporting without remote `UpdateManager` or `SyncQueue` dependencies; shared/team imports retain their remote path.