Checking in progress, though not fully working as expected
This commit is contained in:
@@ -577,9 +577,7 @@ pub fn init_actions_from_parent_view<T: Action + Clone>(
|
||||
)
|
||||
.with_group(bindings::BindingGroup::WarpAi)
|
||||
.is_supported_on_current_platform(
|
||||
UserWorkspaces::as_ref(app).is_byo_api_key_enabled(app)
|
||||
|| (FeatureFlag::CustomInferenceEndpoints.is_enabled()
|
||||
&& UserWorkspaces::as_ref(app).is_custom_inference_enabled(app)),
|
||||
UserWorkspaces::as_ref(app).is_byo_api_key_enabled(app),
|
||||
),
|
||||
ToggleSettingActionPair::new(
|
||||
"auto show or hide Rich Input based on agent status",
|
||||
@@ -7640,8 +7638,6 @@ struct ApiKeysWidget {
|
||||
can_use_warp_credits_for_fallback: SwitchStateHandle,
|
||||
upgrade_highlight_index: HighlightedHyperlink,
|
||||
|
||||
custom_inference_info_tooltip: MouseStateHandle,
|
||||
custom_inference_terms_index: HighlightedHyperlink,
|
||||
description_learn_more_index: HighlightedHyperlink,
|
||||
}
|
||||
|
||||
@@ -7860,8 +7856,6 @@ impl ApiKeysWidget {
|
||||
can_use_warp_credits_for_fallback: Default::default(),
|
||||
upgrade_highlight_index: Default::default(),
|
||||
|
||||
custom_inference_info_tooltip: Default::default(),
|
||||
custom_inference_terms_index: Default::default(),
|
||||
description_learn_more_index: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -7935,175 +7929,6 @@ impl ApiKeysWidget {
|
||||
column.finish()
|
||||
}
|
||||
|
||||
fn render_custom_inference_description(&self, app: &AppContext) -> Box<dyn Element> {
|
||||
let appearance = Appearance::as_ref(app);
|
||||
let text_fragments = vec![
|
||||
FormattedTextFragment::plain_text(
|
||||
"Use your own API keys from model providers for Warp Agent. You can also add custom endpoints to use third-party models. Custom endpoints must support the OpenAI-compatible Chat Completions API. API keys are stored only on your device, never on Warp's servers. They're used to make requests to your chosen model provider. Using auto models or models from providers you have not provided API keys for will consume Warp credits. ",
|
||||
),
|
||||
FormattedTextFragment::hyperlink("Learn more", CUSTOM_INFERENCE_LEARN_MORE_URL),
|
||||
];
|
||||
let description = FormattedTextElement::new(
|
||||
FormattedText::new([FormattedTextLine::Line(text_fragments)]),
|
||||
CONTENT_FONT_SIZE,
|
||||
appearance.ui_font_family(),
|
||||
appearance.ui_font_family(),
|
||||
blended_colors::text_sub(appearance.theme(), appearance.theme().surface_1()),
|
||||
self.description_learn_more_index.clone(),
|
||||
)
|
||||
.with_hyperlink_font_color(appearance.theme().accent().into_solid())
|
||||
.register_default_click_handlers(|url, ctx, _| {
|
||||
ctx.dispatch_typed_action(AISettingsPageAction::HyperlinkClick(url));
|
||||
});
|
||||
Container::new(description.finish())
|
||||
.with_margin_top(styles::DESCRIPTION_NEGATIVE_MARGIN_OFFSET)
|
||||
.with_margin_bottom(styles::DESCRIPTION_MARGIN_BOTTOM)
|
||||
.with_margin_right(styles::TOGGLE_WIDTH_MARGIN)
|
||||
.finish()
|
||||
}
|
||||
|
||||
fn render_custom_inference_info_icon(&self, appearance: &Appearance) -> Box<dyn Element> {
|
||||
let icon = Container::new(
|
||||
ConstrainedBox::new(
|
||||
Icon::Info
|
||||
.to_galaxyui_icon(appearance.theme().active_ui_text_color())
|
||||
.finish(),
|
||||
)
|
||||
.with_width(13.)
|
||||
.with_height(13.)
|
||||
.finish(),
|
||||
)
|
||||
.finish();
|
||||
|
||||
let tooltip_text = FormattedText::new([FormattedTextLine::Line(vec![
|
||||
FormattedTextFragment::plain_text(
|
||||
"By using BYOK or custom endpoints, you agree to use them only as permitted by ",
|
||||
),
|
||||
FormattedTextFragment::hyperlink("Warp's Terms of Service", CUSTOM_INFERENCE_TERMS_URL),
|
||||
FormattedTextFragment::plain_text(
|
||||
". BYOK and custom endpoints are intended for individual use and small teams. Companies or organizations with more than 10 employees should use Warp Business or Enterprise.",
|
||||
),
|
||||
])]);
|
||||
let tooltip_background = appearance.theme().tooltip_background();
|
||||
|
||||
let info_button =
|
||||
Hoverable::new(self.custom_inference_info_tooltip.clone(), move |state| {
|
||||
let mut stack = Stack::new().with_child(icon);
|
||||
if state.is_hovered() {
|
||||
let tool_tip = ConstrainedBox::new(
|
||||
Container::new(
|
||||
FormattedTextElement::new(
|
||||
tooltip_text.clone(),
|
||||
10.,
|
||||
appearance.ui_font_family(),
|
||||
appearance.ui_font_family(),
|
||||
appearance.theme().background().into_solid(),
|
||||
self.custom_inference_terms_index.clone(),
|
||||
)
|
||||
.with_hyperlink_font_color(
|
||||
appearance
|
||||
.theme()
|
||||
.accent()
|
||||
.on_background(
|
||||
ThemeFill::Solid(tooltip_background),
|
||||
MinimumAllowedContrast::Text,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
.register_default_click_handlers(|url, ctx, _| {
|
||||
ctx.dispatch_typed_action(AISettingsPageAction::HyperlinkClick(
|
||||
url,
|
||||
));
|
||||
})
|
||||
.finish(),
|
||||
)
|
||||
.with_background_color(tooltip_background)
|
||||
.with_vertical_padding(4.)
|
||||
.with_horizontal_padding(8.)
|
||||
.with_border(Border::all(1.).with_border_fill(appearance.theme().outline()))
|
||||
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.)))
|
||||
.finish(),
|
||||
)
|
||||
.with_max_width(CUSTOM_INFERENCE_INFO_TOOLTIP_MAX_WIDTH)
|
||||
.finish();
|
||||
stack.add_positioned_child(
|
||||
tool_tip,
|
||||
OffsetPositioning::offset_from_parent(
|
||||
vec2f(0., -3.),
|
||||
ParentOffsetBounds::WindowByPosition,
|
||||
ParentAnchor::TopMiddle,
|
||||
ChildAnchor::BottomMiddle,
|
||||
),
|
||||
);
|
||||
}
|
||||
stack.finish()
|
||||
})
|
||||
.with_cursor(Cursor::PointingHand);
|
||||
|
||||
Container::new(Box::new(info_button))
|
||||
.with_margin_left(4.)
|
||||
.finish()
|
||||
}
|
||||
|
||||
fn render_custom_endpoints_list(
|
||||
&self,
|
||||
view: &AISettingsPageView,
|
||||
appearance: &Appearance,
|
||||
is_enabled: bool,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
let theme = appearance.theme();
|
||||
let text_color = styles::header_font_color(is_enabled, app);
|
||||
let endpoints = &ApiKeyManager::as_ref(app).keys().custom_endpoints;
|
||||
let chip_border = internal_colors::fg_overlay_3(theme);
|
||||
|
||||
let mut list = Flex::column().with_spacing(12.);
|
||||
for (index, endpoint) in endpoints.iter().enumerate() {
|
||||
let model_labels = endpoint
|
||||
.models
|
||||
.iter()
|
||||
.map(|model| model.alias.clone().unwrap_or_else(|| model.name.clone()))
|
||||
.filter(|s| !s.trim().is_empty());
|
||||
|
||||
let chips = super::render_model_chips(model_labels, appearance, text_color);
|
||||
|
||||
let endpoint_name = Text::new_inline(
|
||||
endpoint.name.clone(),
|
||||
appearance.ui_font_family(),
|
||||
appearance.ui_font_size(),
|
||||
)
|
||||
.with_style(Properties::default().weight(Weight::Semibold))
|
||||
.with_color(text_color.into())
|
||||
.finish();
|
||||
|
||||
let left = Flex::column()
|
||||
.with_spacing(8.)
|
||||
.with_child(endpoint_name)
|
||||
.with_child(chips)
|
||||
.finish();
|
||||
|
||||
let edit_button = Empty::new().finish();
|
||||
|
||||
let row = Flex::row()
|
||||
.with_main_axis_size(MainAxisSize::Max)
|
||||
.with_main_axis_alignment(MainAxisAlignment::SpaceBetween)
|
||||
.with_cross_axis_alignment(CrossAxisAlignment::Center)
|
||||
.with_child(Shrinkable::new(1., left).finish())
|
||||
.with_child(edit_button)
|
||||
.finish();
|
||||
|
||||
list.add_child(
|
||||
Container::new(row)
|
||||
.with_uniform_padding(12.)
|
||||
.with_background(internal_colors::fg_overlay_1(theme))
|
||||
.with_border(Border::all(1.).with_border_fill(chip_border))
|
||||
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(6.)))
|
||||
.finish(),
|
||||
);
|
||||
}
|
||||
list.finish()
|
||||
}
|
||||
|
||||
/// The "Connect SuperGrok subscription" row: label and description on the
|
||||
/// left, a Connect/Disconnect button on the right, and a "Connected on
|
||||
/// ..." status line underneath while a subscription is connected.
|
||||
@@ -8299,95 +8124,23 @@ impl SettingsWidget for ApiKeysWidget {
|
||||
let ai_settings = AISettings::as_ref(app);
|
||||
let is_any_ai_enabled = ai_settings.is_any_ai_enabled(app);
|
||||
let is_byo_enabled = UserWorkspaces::as_ref(app).is_byo_api_key_enabled(app);
|
||||
let is_custom_inference_enabled =
|
||||
UserWorkspaces::as_ref(app).is_custom_inference_enabled(app);
|
||||
let provider_keys_enabled = is_any_ai_enabled && is_byo_enabled;
|
||||
let custom_inference_controls_enabled = is_any_ai_enabled && is_custom_inference_enabled;
|
||||
let custom_inference_flag_on = FeatureFlag::CustomInferenceEndpoints.is_enabled();
|
||||
let show_custom_inference = custom_inference_flag_on && is_custom_inference_enabled;
|
||||
|
||||
let mut column = Flex::column().with_child(render_separator(appearance));
|
||||
|
||||
if show_custom_inference {
|
||||
// Header row: "Custom inference" + info icon on left, "+ Add custom model" on right
|
||||
let header_left = Flex::row()
|
||||
.with_cross_axis_alignment(CrossAxisAlignment::Center)
|
||||
.with_child(
|
||||
build_sub_header(
|
||||
appearance,
|
||||
"Custom inference",
|
||||
Some(styles::header_font_color(
|
||||
custom_inference_controls_enabled,
|
||||
app,
|
||||
)),
|
||||
)
|
||||
.with_margin_bottom(0.)
|
||||
.finish(),
|
||||
)
|
||||
.with_child(self.render_custom_inference_info_icon(appearance))
|
||||
.finish();
|
||||
|
||||
let header_row = Flex::row()
|
||||
.with_main_axis_size(MainAxisSize::Max)
|
||||
.with_main_axis_alignment(MainAxisAlignment::SpaceBetween)
|
||||
.with_cross_axis_alignment(CrossAxisAlignment::Center)
|
||||
.with_child(header_left)
|
||||
.finish();
|
||||
|
||||
column.add_child(
|
||||
Container::new(header_row)
|
||||
.with_padding_bottom(HEADER_PADDING)
|
||||
.finish(),
|
||||
);
|
||||
|
||||
// Description with Learn more link
|
||||
column.add_child(self.render_custom_inference_description(app));
|
||||
} else {
|
||||
// Fallback: old "API Keys" header only
|
||||
column.add_child(
|
||||
build_sub_header(
|
||||
appearance,
|
||||
"API Keys",
|
||||
Some(styles::header_font_color(is_any_ai_enabled, app)),
|
||||
)
|
||||
.with_padding_bottom(HEADER_PADDING)
|
||||
.finish(),
|
||||
);
|
||||
}
|
||||
column.add_child(
|
||||
build_sub_header(
|
||||
appearance,
|
||||
"API Keys",
|
||||
Some(styles::header_font_color(is_any_ai_enabled, app)),
|
||||
)
|
||||
.with_padding_bottom(HEADER_PADDING)
|
||||
.finish(),
|
||||
);
|
||||
|
||||
// Provider key editors (always visible)
|
||||
column.add_child(self.render_provider_key_editors(appearance, provider_keys_enabled, app));
|
||||
|
||||
// Custom endpoints sub-label + list (only when flag on and endpoints non-empty)
|
||||
if show_custom_inference {
|
||||
let endpoints = &ApiKeyManager::as_ref(app).keys().custom_endpoints;
|
||||
if !endpoints.is_empty() {
|
||||
column.add_child(
|
||||
Container::new(
|
||||
Text::new_inline(
|
||||
"Custom endpoints",
|
||||
appearance.ui_font_family(),
|
||||
CONTENT_FONT_SIZE,
|
||||
)
|
||||
.with_color(
|
||||
styles::header_font_color(custom_inference_controls_enabled, app)
|
||||
.into(),
|
||||
)
|
||||
.with_style(Properties::default().weight(Weight::Semibold))
|
||||
.finish(),
|
||||
)
|
||||
.with_margin_top(16.)
|
||||
.with_margin_bottom(8.)
|
||||
.finish(),
|
||||
);
|
||||
column.add_child(self.render_custom_endpoints_list(
|
||||
view,
|
||||
appearance,
|
||||
custom_inference_controls_enabled,
|
||||
app,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Entrypoint for connecting a SuperGrok (xAI) subscription via OAuth.
|
||||
if FeatureFlag::SuperGrok.is_enabled() {
|
||||
@@ -8420,8 +8173,8 @@ impl SettingsWidget for ApiKeysWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// Warp credit fallback toggle (shown when BYO or custom inference is enabled)
|
||||
if is_byo_enabled || show_custom_inference {
|
||||
// Warp credit fallback toggle (shown when BYO is enabled)
|
||||
if is_byo_enabled {
|
||||
column.add_child(
|
||||
Container::new(self.render_warp_credit_fallback_toggle(view, app))
|
||||
.with_margin_top(16.)
|
||||
|
||||
@@ -454,7 +454,6 @@ pub mod flags {
|
||||
pub const TELEMETRY_FLAG: &str = "telemetry";
|
||||
pub const SETTINGS_SYNC_FLAG: &str = "settings_sync";
|
||||
pub const SAFE_MODE_FLAG: &str = "safe_mode";
|
||||
pub const CRASH_REPORTING_FLAG: &str = "crash_reporting";
|
||||
pub const CLOUD_CONVERSATION_STORAGE_FLAG: &str = "Cloud_Conversation_Storage_Enabled";
|
||||
pub const CLOUD_CONVERSATION_STORAGE_EDITABLE_FLAG: &str =
|
||||
"Cloud_Conversation_Storage_Editable";
|
||||
@@ -1885,11 +1884,6 @@ impl SettingsView {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "crash_reporting")]
|
||||
{
|
||||
crate::crash_reporting::set_tag("warp.settings_page", section.to_string());
|
||||
}
|
||||
|
||||
if let Some(settings_page) = self.current_settings_page() {
|
||||
update_page!(
|
||||
&settings_page.view_handle,
|
||||
|
||||
@@ -279,17 +279,6 @@ impl PrivacyPageView {
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
fn toggle_crash_reporting(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
let privacy_settings_handle = PrivacySettings::handle(ctx);
|
||||
let old_value = privacy_settings_handle
|
||||
.as_ref(ctx)
|
||||
.is_crash_reporting_enabled;
|
||||
ctx.update_model(&privacy_settings_handle, |privacy_settings, ctx| {
|
||||
privacy_settings.set_is_crash_reporting_enabled(!old_value, ctx);
|
||||
});
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
fn queue_regex_removal(&mut self, idx: usize, ctx: &mut ViewContext<Self>) {
|
||||
// Check if this removal is already pending
|
||||
if self.pending_regex_removals.contains(&idx) {
|
||||
@@ -454,7 +443,6 @@ pub enum PrivacyPageAction {
|
||||
ToggleHideSecretsInBlockList,
|
||||
SetSecretDisplayMode(SecretDisplayMode),
|
||||
ToggleTelemetry,
|
||||
ToggleCrashReporting,
|
||||
LaunchNetworkLogging,
|
||||
RemoveCustomRegex(usize),
|
||||
AddAllRecommendedRegexes,
|
||||
@@ -534,7 +522,6 @@ impl TypedActionView for PrivacyPageView {
|
||||
self.set_secret_display_mode(*mode, ctx)
|
||||
}
|
||||
PrivacyPageAction::ToggleTelemetry => self.toggle_telemetry(ctx),
|
||||
PrivacyPageAction::ToggleCrashReporting => self.toggle_crash_reporting(ctx),
|
||||
PrivacyPageAction::ToggleCloudConversationStorage => {
|
||||
let handle = PrivacySettings::handle(ctx);
|
||||
ctx.update_model(&handle, |settings, ctx| {
|
||||
@@ -1539,11 +1526,6 @@ impl SettingsWidget for CrashReportsWidget {
|
||||
}
|
||||
|
||||
fn should_render(&self, app: &AppContext) -> bool {
|
||||
// Builds without a crash reporting config (e.g. OpenWarp) cannot ship
|
||||
// crash reports, so the toggle would be a no-op. Hide it in that case.
|
||||
if !ChannelState::is_crash_reporting_available() {
|
||||
return false;
|
||||
}
|
||||
let privacy_settings = PrivacySettings::as_ref(app);
|
||||
!privacy_settings.is_telemetry_force_enabled()
|
||||
}
|
||||
@@ -1555,25 +1537,7 @@ impl SettingsWidget for CrashReportsWidget {
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
let ui_builder = appearance.ui_builder();
|
||||
let privacy_settings = PrivacySettings::as_ref(app);
|
||||
Flex::column()
|
||||
.with_child(render_body_item::<PrivacyPageAction>(
|
||||
"Send crash reports".into(),
|
||||
None,
|
||||
// Crash report state is always synced to cloud, so no need to show local only icon.
|
||||
LocalOnlyIconState::Hidden,
|
||||
ToggleState::Enabled,
|
||||
appearance,
|
||||
ui_builder
|
||||
.switch(self.switch_state.clone())
|
||||
.check(privacy_settings.is_crash_reporting_enabled)
|
||||
.build()
|
||||
.on_click(move |ctx, _, _| {
|
||||
ctx.dispatch_typed_action(PrivacyPageAction::ToggleCrashReporting)
|
||||
})
|
||||
.finish(),
|
||||
None,
|
||||
))
|
||||
.with_child(
|
||||
ui_builder
|
||||
.paragraph(
|
||||
@@ -1692,14 +1656,6 @@ pub fn init_actions_from_parent_view<T: Action + Clone>(
|
||||
context,
|
||||
flags::TELEMETRY_FLAG,
|
||||
),
|
||||
ToggleSettingActionPair::new(
|
||||
"crash reporting",
|
||||
builder(SettingsAction::PrivacyPageToggle(
|
||||
PrivacyPageAction::ToggleCrashReporting,
|
||||
)),
|
||||
context,
|
||||
flags::CRASH_REPORTING_FLAG,
|
||||
),
|
||||
];
|
||||
|
||||
toggle_binding_pairs.push(ToggleSettingActionPair::new(
|
||||
|
||||
Reference in New Issue
Block a user