Remove account billing UI from AI settings
This commit is contained in:
@@ -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<Self>) {
|
||||
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<Self>) -> 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<dyn galaxyui::Element> {
|
||||
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<Cow<'static, str>>,
|
||||
description: impl Into<Cow<'static, str>>,
|
||||
used: usize,
|
||||
limit: usize,
|
||||
is_unlimited: bool,
|
||||
workspace_is_delinquent_due_to_payment_issue: bool,
|
||||
appearance: &Appearance,
|
||||
) -> Box<dyn galaxyui::Element> {
|
||||
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<dyn Element> {
|
||||
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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user