Significant progress. Performing cleanup now
This commit is contained in:
@@ -28,9 +28,9 @@ use crate::settings::{
|
||||
CanUseWarpCreditsWithByok, CodeSettings, CodebaseContextEnabled, FileBasedMcpEnabled,
|
||||
GitOperationsAutogenEnabled, IncludeAgentCommandsInHistory, IntelligentAutosuggestionsEnabled,
|
||||
MemoryEnabled, NLDInTerminalEnabled, NaturalLanguageAutosuggestionsEnabled,
|
||||
OrchestrationEnabled, RuleSuggestionsEnabled, SharedBlockTitleGenerationEnabled,
|
||||
RuleSuggestionsEnabled, SharedBlockTitleGenerationEnabled,
|
||||
ShouldRenderCLIAgentToolbar, ShouldRenderUseAgentToolbarForUserCommands,
|
||||
ShouldShowOzUpdatesInZeroState, ShowAgentTips, ShowConversationHistory, ShowHintText,
|
||||
ShowAgentTips, ShowConversationHistory, ShowHintText,
|
||||
ThinkingDisplayMode, VoiceInputEnabled, WarpDriveContextEnabled,
|
||||
};
|
||||
use crate::terminal::session_settings::{SessionSettings, SessionSettingsChangedEvent};
|
||||
@@ -269,29 +269,6 @@ pub fn init_actions_from_parent_view<T: Action + Clone>(
|
||||
.with_enabled(|| FeatureFlag::AgentTips.is_enabled())],
|
||||
app,
|
||||
);
|
||||
ToggleSettingActionPair::add_toggle_setting_action_pairs_as_bindings(
|
||||
vec![ToggleSettingActionPair::custom(
|
||||
SettingActionPairDescriptions::new(
|
||||
"Show Oz changelog in new agent conversation view",
|
||||
"Hide Oz changelog in new agent conversation view",
|
||||
),
|
||||
builder(SettingsAction::AI(
|
||||
AISettingsPageAction::ToggleShowOzUpdatesInZeroState,
|
||||
)),
|
||||
SettingActionPairContexts::new(
|
||||
context.clone()
|
||||
& id!(flags::IS_ANY_AI_ENABLED)
|
||||
& !id!(flags::SHOW_OZ_UPDATES_IN_ZERO_STATE_FLAG),
|
||||
context.clone()
|
||||
& id!(flags::IS_ANY_AI_ENABLED)
|
||||
& id!(flags::SHOW_OZ_UPDATES_IN_ZERO_STATE_FLAG),
|
||||
),
|
||||
None,
|
||||
)
|
||||
.with_group(bindings::BindingGroup::WarpAi)
|
||||
.with_enabled(|| FeatureFlag::AgentView.is_enabled())],
|
||||
app,
|
||||
);
|
||||
{
|
||||
use crate::settings::ThinkingDisplayMode;
|
||||
use galaxyui::keymap::FixedBinding;
|
||||
@@ -1468,12 +1445,8 @@ impl AISettingsPageView {
|
||||
widgets.push(Box::new(VoiceWidget::default()));
|
||||
}
|
||||
widgets.push(Box::new(CLIAgentWidget::default()));
|
||||
widgets.push(Box::new(ApiKeysWidget::new(ctx)));
|
||||
widgets.push(Box::new(AgentAttributionWidget::default()));
|
||||
widgets.push(Box::new(OtherAIWidget::default()));
|
||||
if FeatureFlag::AgentModeComputerUse.is_enabled() {
|
||||
widgets.push(Box::new(CloudAgentComputerUseWidget::default()));
|
||||
}
|
||||
}
|
||||
Some(AISubpage::WarpAgent) => {
|
||||
// Oz page: global toggle + Active AI + Input + Other
|
||||
@@ -1507,12 +1480,8 @@ impl AISettingsPageView {
|
||||
if voice_supported {
|
||||
widgets.push(Box::new(VoiceWidget::default()));
|
||||
}
|
||||
widgets.push(Box::new(ApiKeysWidget::new(ctx)));
|
||||
widgets.push(Box::new(AgentAttributionWidget::default()));
|
||||
widgets.push(Box::new(OtherAIWidget::default()));
|
||||
if FeatureFlag::AgentModeComputerUse.is_enabled() {
|
||||
widgets.push(Box::new(CloudAgentComputerUseWidget::default()));
|
||||
}
|
||||
}
|
||||
Some(AISubpage::Profiles) => {
|
||||
if !FeatureFlag::UsageBasedPricing.is_enabled() {
|
||||
@@ -2076,7 +2045,6 @@ pub enum AISettingsPageAction {
|
||||
ToggleCodebaseContext,
|
||||
ToggleShowInputHintText,
|
||||
ToggleShowAgentTips,
|
||||
ToggleShowOzUpdatesInZeroState,
|
||||
SetThinkingDisplayMode(ThinkingDisplayMode),
|
||||
AttemptLoginGatedUpgrade,
|
||||
RemoveCLIAgentToolbarEnabledCommand(String),
|
||||
@@ -2116,13 +2084,11 @@ pub enum AISettingsPageAction {
|
||||
SetBedrockProfile(String),
|
||||
ToggleBedrockCrossRegionInference,
|
||||
ToggleBedrockFallbackToWarp,
|
||||
ToggleCloudAgentComputerUse,
|
||||
ToggleFileBasedMcp,
|
||||
ToggleIncludeAgentCommandsInHistory,
|
||||
ToggleAgentAttribution,
|
||||
#[cfg(feature = "local_fs")]
|
||||
SetConversationLayout(crate::util::file::external_editor::settings::OpenConversationPreference),
|
||||
ToggleOrchestration,
|
||||
ToggleShowConversationHistory,
|
||||
ToggleAutoToggleRichInput,
|
||||
ToggleAutoOpenRichInputOnCLIAgentStart,
|
||||
@@ -2504,14 +2470,6 @@ impl TypedActionView for AISettingsPageView {
|
||||
});
|
||||
ctx.notify();
|
||||
}
|
||||
AISettingsPageAction::ToggleShowOzUpdatesInZeroState => {
|
||||
AISettings::handle(ctx).update(ctx, |settings, ctx| {
|
||||
report_if_error!(settings
|
||||
.should_show_oz_updates_in_zero_state
|
||||
.toggle_and_save_value(ctx));
|
||||
});
|
||||
ctx.notify();
|
||||
}
|
||||
AISettingsPageAction::SetThinkingDisplayMode(mode) => {
|
||||
AISettings::handle(ctx).update(ctx, |settings, ctx| {
|
||||
report_if_error!(settings.thinking_display_mode.set_value(*mode, ctx));
|
||||
@@ -2831,14 +2789,6 @@ impl TypedActionView for AISettingsPageView {
|
||||
});
|
||||
ctx.notify();
|
||||
}
|
||||
AISettingsPageAction::ToggleCloudAgentComputerUse => {
|
||||
AISettings::handle(ctx).update(ctx, |settings, ctx| {
|
||||
report_if_error!(settings
|
||||
.cloud_agent_computer_use_enabled
|
||||
.toggle_and_save_value(ctx));
|
||||
});
|
||||
ctx.notify();
|
||||
}
|
||||
AISettingsPageAction::ToggleFileBasedMcp => {
|
||||
AISettings::handle(ctx).update(ctx, |settings, ctx| {
|
||||
report_if_error!(settings.file_based_mcp_enabled.toggle_and_save_value(ctx));
|
||||
@@ -2872,12 +2822,6 @@ impl TypedActionView for AISettingsPageView {
|
||||
);
|
||||
ctx.notify();
|
||||
}
|
||||
AISettingsPageAction::ToggleOrchestration => {
|
||||
AISettings::handle(ctx).update(ctx, |settings, ctx| {
|
||||
report_if_error!(settings.orchestration_enabled.toggle_and_save_value(ctx));
|
||||
});
|
||||
ctx.notify();
|
||||
}
|
||||
AISettingsPageAction::ToggleShowConversationHistory => {
|
||||
AISettings::handle(ctx).update(ctx, |settings, ctx| {
|
||||
report_if_error!(settings
|
||||
@@ -5345,7 +5289,6 @@ impl SettingsWidget for VoiceWidget {
|
||||
}
|
||||
#[derive(Default)]
|
||||
struct OtherAIWidget {
|
||||
show_oz_updates_in_zero_state_toggle: SwitchStateHandle,
|
||||
use_agent_footer_toggle: SwitchStateHandle,
|
||||
show_conversation_history_toggle: SwitchStateHandle,
|
||||
}
|
||||
@@ -5405,15 +5348,6 @@ impl SettingsWidget for OtherAIWidget {
|
||||
|
||||
if FeatureFlag::AgentView.is_enabled() {
|
||||
let mut agent_view_column = Flex::column()
|
||||
.with_child(render_ai_setting_toggle::<ShouldShowOzUpdatesInZeroState>(
|
||||
"Show Oz changelog in new conversation view",
|
||||
AISettingsPageAction::ToggleShowOzUpdatesInZeroState,
|
||||
*ai_settings.should_show_oz_updates_in_zero_state,
|
||||
is_toggleable,
|
||||
self.show_oz_updates_in_zero_state_toggle.clone(),
|
||||
&view.local_only_icon_tooltip_states,
|
||||
app,
|
||||
))
|
||||
.with_child(render_ai_setting_toggle::<ShouldRenderUseAgentToolbarForUserCommands>(
|
||||
"Show \"Use Agent\" footer",
|
||||
AISettingsPageAction::ToggleUseAgentToolbar,
|
||||
@@ -5902,462 +5836,6 @@ impl SettingsWidget for AgentAttributionWidget {
|
||||
#[path = "ai_page_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
#[derive(Default)]
|
||||
struct CloudAgentComputerUseWidget {
|
||||
toggle: SwitchStateHandle,
|
||||
orchestration_toggle: SwitchStateHandle,
|
||||
}
|
||||
|
||||
impl SettingsWidget for CloudAgentComputerUseWidget {
|
||||
type View = AISettingsPageView;
|
||||
|
||||
fn search_terms(&self) -> &str {
|
||||
"oz cloud agent computer use orchestration multi-agent"
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
view: &Self::View,
|
||||
appearance: &Appearance,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
use crate::ai::execution_profiles::{CloudAgentComputerUseState, ComputerUsePermission};
|
||||
|
||||
let is_any_ai_enabled = AISettings::as_ref(app).is_any_ai_enabled(app);
|
||||
|
||||
// Determine toggle state based on workspace autonomy setting and user preference
|
||||
let CloudAgentComputerUseState {
|
||||
enabled: is_checked,
|
||||
is_forced_by_org,
|
||||
} = ComputerUsePermission::resolve_cloud_agent_state(app);
|
||||
|
||||
// Toggle is disabled if forced by org settings OR if AI is globally disabled
|
||||
let is_disabled = is_forced_by_org || !is_any_ai_enabled;
|
||||
|
||||
let ui_builder = appearance.ui_builder();
|
||||
let toggle = if is_forced_by_org {
|
||||
// Disabled by organization setting - show tooltip on hover
|
||||
ui_builder
|
||||
.switch(self.toggle.clone())
|
||||
.check(is_checked)
|
||||
.with_tooltip(TooltipConfig {
|
||||
text: "This option is enforced by your organization's settings and cannot be customized.".to_string(),
|
||||
styles: ui_builder.default_tool_tip_styles(),
|
||||
})
|
||||
.disable()
|
||||
.build()
|
||||
.finish()
|
||||
} else if !is_any_ai_enabled {
|
||||
// Disabled because AI is off globally - no tooltip needed
|
||||
ui_builder
|
||||
.switch(self.toggle.clone())
|
||||
.check(is_checked)
|
||||
.with_disabled(true)
|
||||
.build()
|
||||
.finish()
|
||||
} else {
|
||||
// Enabled - allow toggling
|
||||
ui_builder
|
||||
.switch(self.toggle.clone())
|
||||
.check(is_checked)
|
||||
.build()
|
||||
.on_click(move |ctx, _, _| {
|
||||
ctx.dispatch_typed_action(AISettingsPageAction::ToggleCloudAgentComputerUse);
|
||||
})
|
||||
.finish()
|
||||
};
|
||||
|
||||
let toggle_row = build_toggle_element(
|
||||
render_body_item_label::<AISettingsPageAction>(
|
||||
"Computer use in Cloud Agents".to_string(),
|
||||
Some(styles::header_font_color(!is_disabled, app)),
|
||||
None,
|
||||
LocalOnlyIconState::Hidden,
|
||||
ToggleState::Enabled,
|
||||
appearance,
|
||||
),
|
||||
toggle,
|
||||
appearance,
|
||||
None,
|
||||
);
|
||||
|
||||
let mut column = Flex::column()
|
||||
.with_child(render_separator(appearance))
|
||||
.with_child(
|
||||
build_sub_header(
|
||||
appearance,
|
||||
"Experimental",
|
||||
Some(styles::header_font_color(is_any_ai_enabled, app)),
|
||||
)
|
||||
.with_padding_bottom(HEADER_PADDING)
|
||||
.finish(),
|
||||
)
|
||||
.with_child(toggle_row)
|
||||
.with_child(render_ai_setting_description(
|
||||
"Enable computer use in cloud agent conversations started from the Warp app.",
|
||||
!is_disabled,
|
||||
app,
|
||||
));
|
||||
|
||||
if FeatureFlag::Orchestration.is_enabled() {
|
||||
let ai_settings = AISettings::as_ref(app);
|
||||
column.add_child(render_ai_setting_toggle::<OrchestrationEnabled>(
|
||||
"Orchestration",
|
||||
AISettingsPageAction::ToggleOrchestration,
|
||||
*ai_settings.orchestration_enabled,
|
||||
is_any_ai_enabled,
|
||||
self.orchestration_toggle.clone(),
|
||||
&view.local_only_icon_tooltip_states,
|
||||
app,
|
||||
));
|
||||
column.add_child(render_ai_setting_description(
|
||||
"Enable multi-agent orchestration, allowing the agent to spawn and coordinate parallel sub-agents.",
|
||||
is_any_ai_enabled,
|
||||
app,
|
||||
));
|
||||
}
|
||||
|
||||
column.finish()
|
||||
}
|
||||
}
|
||||
|
||||
struct ApiKeysWidget {
|
||||
openai_api_key_editor: ViewHandle<EditorView>,
|
||||
anthropic_api_key_editor: ViewHandle<EditorView>,
|
||||
google_api_key_editor: ViewHandle<EditorView>,
|
||||
|
||||
can_use_warp_credits_with_byok: SwitchStateHandle,
|
||||
upgrade_highlight_index: HighlightedHyperlink,
|
||||
}
|
||||
|
||||
impl ApiKeysWidget {
|
||||
fn new(ctx: &mut ViewContext<<Self as SettingsWidget>::View>) -> Self {
|
||||
let ai_settings = AISettings::as_ref(ctx);
|
||||
let workspace_handle = UserWorkspaces::handle(ctx);
|
||||
let is_any_ai_enabled = ai_settings.is_any_ai_enabled(ctx);
|
||||
let is_byo_enabled = workspace_handle.as_ref(ctx).is_byo_api_key_enabled();
|
||||
|
||||
let ApiKeys {
|
||||
openai: openai_key,
|
||||
anthropic: anthropic_key,
|
||||
google: google_key,
|
||||
..
|
||||
} = ApiKeyManager::as_ref(ctx).keys().clone();
|
||||
|
||||
// A helper macro to create and configure an API key editor. This avoids a lot
|
||||
// of code duplication and ensures consistency between the editors.
|
||||
macro_rules! create_api_key_editor {
|
||||
($editor:ident, $key:ident, $set_func:ident, $placeholder:literal) => {
|
||||
let $editor = ctx.add_typed_action_view(move |ctx| {
|
||||
let appearance = Appearance::handle(ctx).as_ref(ctx);
|
||||
let options = SingleLineEditorOptions {
|
||||
is_password: true,
|
||||
text: TextOptions {
|
||||
font_size_override: Some(appearance.ui_font_size()),
|
||||
font_family_override: Some(appearance.monospace_font_family()),
|
||||
text_colors_override: Some(TextColors {
|
||||
default_color: appearance.theme().active_ui_text_color(),
|
||||
disabled_color: appearance.theme().disabled_ui_text_color(),
|
||||
hint_color: appearance.theme().disabled_ui_text_color(),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
let mut editor = EditorView::single_line(options, ctx);
|
||||
editor.set_placeholder_text($placeholder, ctx);
|
||||
if let Some(key) = &$key {
|
||||
editor.set_buffer_text(key, ctx);
|
||||
}
|
||||
editor
|
||||
});
|
||||
AISettingsPageView::update_editor_interaction_state(
|
||||
$editor.clone(),
|
||||
is_any_ai_enabled && is_byo_enabled,
|
||||
ctx,
|
||||
);
|
||||
ctx.subscribe_to_view(&$editor, |_, $editor, event, ctx| {
|
||||
if matches!(event, EditorEvent::Blurred | EditorEvent::Enter) {
|
||||
let buffer_text = $editor.as_ref(ctx).buffer_text(ctx);
|
||||
let key = buffer_text.is_empty().not().then_some(buffer_text);
|
||||
ApiKeyManager::handle(ctx).update(ctx, |model, ctx| {
|
||||
model.$set_func(key, ctx);
|
||||
});
|
||||
}
|
||||
});
|
||||
let editor_clone = $editor.clone();
|
||||
ctx.subscribe_to_model(&workspace_handle, move |_, workspace, event, ctx| {
|
||||
if let UserWorkspacesEvent::TeamsChanged = event {
|
||||
let is_any_ai_enabled =
|
||||
AISettings::handle(ctx).as_ref(ctx).is_any_ai_enabled(ctx);
|
||||
let is_byo_enabled = workspace.as_ref(ctx).is_byo_api_key_enabled();
|
||||
let is_enabled = is_any_ai_enabled && is_byo_enabled;
|
||||
let has_key = !editor_clone.as_ref(ctx).is_empty(ctx);
|
||||
|
||||
// If BYO is disabled, clear the API key from the editor and storage
|
||||
if !is_byo_enabled && has_key {
|
||||
editor_clone.update(ctx, |editor, ctx| {
|
||||
editor.set_buffer_text("", ctx);
|
||||
});
|
||||
ApiKeyManager::handle(ctx).update(ctx, |model, ctx| {
|
||||
model.$set_func(None, ctx);
|
||||
});
|
||||
}
|
||||
|
||||
AISettingsPageView::update_editor_interaction_state(
|
||||
editor_clone.clone(),
|
||||
is_enabled,
|
||||
ctx,
|
||||
);
|
||||
ctx.notify();
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
create_api_key_editor!(openai_api_key_editor, openai_key, set_openai_key, "sk-...");
|
||||
create_api_key_editor!(
|
||||
anthropic_api_key_editor,
|
||||
anthropic_key,
|
||||
set_anthropic_key,
|
||||
"sk-ant-..."
|
||||
);
|
||||
create_api_key_editor!(
|
||||
google_api_key_editor,
|
||||
google_key,
|
||||
set_google_key,
|
||||
"AIzaSy..."
|
||||
);
|
||||
|
||||
Self {
|
||||
openai_api_key_editor,
|
||||
anthropic_api_key_editor,
|
||||
google_api_key_editor,
|
||||
|
||||
can_use_warp_credits_with_byok: Default::default(),
|
||||
upgrade_highlight_index: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_api_keys_section(
|
||||
&self,
|
||||
appearance: &Appearance,
|
||||
app: &AppContext,
|
||||
is_byo_enabled: bool,
|
||||
) -> Box<dyn Element> {
|
||||
let ai_settings = AISettings::as_ref(app);
|
||||
let is_any_ai_enabled = ai_settings.is_any_ai_enabled(app);
|
||||
let is_enabled = is_any_ai_enabled && is_byo_enabled;
|
||||
|
||||
let mut column = Flex::column()
|
||||
.with_spacing(16.)
|
||||
.with_child(
|
||||
Container::new(
|
||||
render_ai_setting_description(
|
||||
"Use your own API keys from model providers for the Warp Agent to use. API keys are stored locally and never synced to the cloud. Using auto models or models from providers you have not provided API keys for will consume Warp credits.",
|
||||
is_enabled,
|
||||
app,
|
||||
))
|
||||
// Remove the bottom margin of the description so that it doesn't
|
||||
// create extra space between the description and the API key inputs.
|
||||
.with_margin_bottom(-styles::DESCRIPTION_MARGIN_BOTTOM).finish()
|
||||
);
|
||||
|
||||
/// Helper function to render the UI for an API key input field.
|
||||
fn render_api_key_input(
|
||||
appearance: &Appearance,
|
||||
label: &'static str,
|
||||
editor: ViewHandle<EditorView>,
|
||||
is_enabled: bool,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
let padding = Some(Coords {
|
||||
top: 10.,
|
||||
bottom: 10.,
|
||||
left: 16.,
|
||||
right: 16.,
|
||||
});
|
||||
let editor_style = UiComponentStyles {
|
||||
padding,
|
||||
background: Some(appearance.theme().surface_2().into()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let label = Text::new_inline(label, appearance.ui_font_family(), CONTENT_FONT_SIZE)
|
||||
.with_color(styles::header_font_color(is_enabled, app).into())
|
||||
.finish();
|
||||
|
||||
let input = appearance
|
||||
.ui_builder()
|
||||
.text_input(editor)
|
||||
.with_style(editor_style)
|
||||
.build()
|
||||
.finish();
|
||||
|
||||
Flex::column()
|
||||
.with_spacing(8.)
|
||||
.with_child(label)
|
||||
.with_child(input)
|
||||
.finish()
|
||||
}
|
||||
|
||||
column.add_child(render_api_key_input(
|
||||
appearance,
|
||||
"OpenAI API Key",
|
||||
self.openai_api_key_editor.clone(),
|
||||
is_enabled,
|
||||
app,
|
||||
));
|
||||
column.add_child(render_api_key_input(
|
||||
appearance,
|
||||
"Anthropic API Key",
|
||||
self.anthropic_api_key_editor.clone(),
|
||||
is_enabled,
|
||||
app,
|
||||
));
|
||||
column.add_child(render_api_key_input(
|
||||
appearance,
|
||||
"Google API Key",
|
||||
self.google_api_key_editor.clone(),
|
||||
is_enabled,
|
||||
app,
|
||||
));
|
||||
|
||||
// Show upgrade CTA if BYOK is not enabled
|
||||
if !is_byo_enabled {
|
||||
let auth_state = AuthStateProvider::as_ref(app).get();
|
||||
let upgrade_text_fragments = if let Some(team) =
|
||||
UserWorkspaces::as_ref(app).current_team()
|
||||
{
|
||||
// Enterprise teams don't have a self-serve upgrade path; route them
|
||||
// to sales to enable BYOK on their existing plan.
|
||||
if team.billing_metadata.customer_type == CustomerType::Enterprise {
|
||||
vec![
|
||||
FormattedTextFragment::hyperlink("Contact sales", "mailto:sales@warp.dev"),
|
||||
FormattedTextFragment::plain_text(
|
||||
" to enable bringing your own API keys on your Enterprise plan.",
|
||||
),
|
||||
]
|
||||
} else {
|
||||
let current_user_email = auth_state.user_email().unwrap_or_default();
|
||||
let has_admin_permissions = team.has_admin_permissions(¤t_user_email);
|
||||
let upgrade_url = UserWorkspaces::upgrade_link_for_team(team.uid);
|
||||
if has_admin_permissions {
|
||||
vec![
|
||||
FormattedTextFragment::hyperlink(
|
||||
"Upgrade to the Build plan",
|
||||
upgrade_url,
|
||||
),
|
||||
FormattedTextFragment::plain_text(" to use your own API keys."),
|
||||
]
|
||||
} else {
|
||||
vec![FormattedTextFragment::plain_text(
|
||||
"Ask your team's admin to upgrade to the Build plan to use your own API keys.",
|
||||
)]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let user_id = auth_state.user_id().unwrap_or_default();
|
||||
let upgrade_url = UserWorkspaces::upgrade_link(user_id);
|
||||
vec![
|
||||
FormattedTextFragment::hyperlink("Upgrade to the Build plan", upgrade_url),
|
||||
FormattedTextFragment::plain_text(" to use your own API keys."),
|
||||
]
|
||||
};
|
||||
|
||||
let upgrade_text_element = FormattedTextElement::new(
|
||||
FormattedText::new([FormattedTextLine::Line(upgrade_text_fragments)]),
|
||||
appearance.ui_font_size(),
|
||||
appearance.ui_font_family(),
|
||||
appearance.ui_font_family(),
|
||||
blended_colors::text_sub(appearance.theme(), appearance.theme().surface_1()),
|
||||
self.upgrade_highlight_index.clone(),
|
||||
)
|
||||
.with_hyperlink_font_color(appearance.theme().accent().into_solid())
|
||||
.register_default_click_handlers(|url, ctx, _| {
|
||||
ctx.dispatch_typed_action(AISettingsPageAction::HyperlinkClick(url));
|
||||
});
|
||||
|
||||
column.add_child(Container::new(upgrade_text_element.finish()).finish());
|
||||
}
|
||||
|
||||
column.finish()
|
||||
}
|
||||
|
||||
fn render_can_use_warp_credits_with_byok_toggle(
|
||||
&self,
|
||||
view: &AISettingsPageView,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
let ai_settings = AISettings::as_ref(app);
|
||||
|
||||
let toggle = render_ai_setting_toggle::<CanUseWarpCreditsWithByok>(
|
||||
"Warp credit fallback",
|
||||
AISettingsPageAction::ToggleCanUseWarpCreditsWithByok,
|
||||
*ai_settings.can_use_warp_credits_with_byok,
|
||||
ai_settings.is_any_ai_enabled(app),
|
||||
self.can_use_warp_credits_with_byok.clone(),
|
||||
&view.local_only_icon_tooltip_states,
|
||||
app,
|
||||
);
|
||||
|
||||
let description = render_ai_setting_description(
|
||||
"When enabled, agent requests may be routed to one of Warp's provided models in the event of an error. Warp will prioritize using your API keys over your Warp credits.",
|
||||
ai_settings.is_any_ai_enabled(app),
|
||||
app,
|
||||
);
|
||||
|
||||
Flex::column()
|
||||
.with_child(toggle)
|
||||
.with_child(description)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl SettingsWidget for ApiKeysWidget {
|
||||
type View = AISettingsPageView;
|
||||
|
||||
fn search_terms(&self) -> &str {
|
||||
"api keys bring your own byo openai anthropic google claude gemini gpt"
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
view: &Self::View,
|
||||
appearance: &Appearance,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
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();
|
||||
|
||||
let mut column = Flex::column()
|
||||
.with_child(render_separator(appearance))
|
||||
.with_child(
|
||||
build_sub_header(
|
||||
appearance,
|
||||
"API Keys",
|
||||
Some(styles::header_font_color(is_any_ai_enabled, app)),
|
||||
)
|
||||
.with_padding_bottom(HEADER_PADDING)
|
||||
.finish(),
|
||||
)
|
||||
.with_child(self.render_api_keys_section(appearance, app, is_byo_enabled));
|
||||
|
||||
if is_byo_enabled {
|
||||
column.add_child(
|
||||
Container::new(self.render_can_use_warp_credits_with_byok_toggle(view, app))
|
||||
.with_margin_top(16.)
|
||||
.finish(),
|
||||
);
|
||||
}
|
||||
|
||||
Container::new(column.finish())
|
||||
.with_margin_bottom(HEADER_PADDING)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
struct BedrockSettingsWidget {
|
||||
enabled_toggle: SwitchStateHandle,
|
||||
cross_region_toggle: SwitchStateHandle,
|
||||
|
||||
Reference in New Issue
Block a user