first pass of merging in warp (doesn't build)

This commit is contained in:
Ryan Ward
2026-07-01 16:08:58 -05:00
parent 2f64909469
commit 4770ac06b5
3662 changed files with 414574 additions and 89772 deletions
@@ -1,28 +1,69 @@
use crate::ai::execution_profiles::{AIExecutionProfile, ActionPermission};
use pathfinder_geometry::vector::vec2f;
use thousands::Separable;
use uuid::Uuid;
use galaxy_core::features::FeatureFlag;
use warpui::elements::{
ChildAnchor, ChildView, ConstrainedBox, Container, CrossAxisAlignment, Dismiss, Flex,
Hoverable, MainAxisAlignment, MainAxisSize, MouseStateHandle, OffsetPositioning, ParentAnchor,
ParentElement, ParentOffsetBounds, Shrinkable, Stack, Text,
};
use warpui::fonts::{Properties, Weight};
use warpui::ui_components::components::{Coords, UiComponent, UiComponentStyles};
use warpui::{AppContext, Element, SingletonEntity, ViewHandle};
use super::{ExecutionProfileEditorView, ExecutionProfileEditorViewAction};
use crate::ai::blocklist::BlocklistAIPermissions;
use crate::ai::execution_profiles::{
long_context_pricing_warning_title, AIExecutionProfile, AIExecutionProfileAppExt as _,
ActionPermission,
};
use crate::editor::EditorView;
use crate::settings::AISettings;
use crate::ui_components::icons::Icon;
use crate::view_components::FilterableDropdown;
use crate::view_components::{Dropdown, SubmittableTextInput};
use crate::Appearance;
use crate::TemplatableMCPServerManager;
use galaxy_core::features::FeatureFlag;
use galaxyui::elements::Hoverable;
use galaxyui::elements::MouseStateHandle;
use galaxyui::elements::{
ChildAnchor, ChildView, ConstrainedBox, Container, CrossAxisAlignment, Flex, MainAxisAlignment,
MainAxisSize, OffsetPositioning, ParentAnchor, ParentElement, ParentOffsetBounds, Shrinkable,
Stack, Text,
use crate::view_components::{
render_warning_box, Dropdown, DropdownItemAction, FilterableDropdown, SubmittableTextInput,
WarningBoxConfig,
};
use galaxyui::fonts::{Properties, Weight};
use galaxyui::ui_components::components::UiComponent;
use galaxyui::AppContext;
use galaxyui::{Element, SingletonEntity, ViewHandle};
use pathfinder_geometry::vector::vec2f;
use uuid::Uuid;
use crate::{Appearance, TemplatableMCPServerManager};
use super::ExecutionProfileEditorView;
use super::ExecutionProfileEditorViewAction;
const CONTEXT_WINDOW_SLIDER_WIDTH: f32 = 220.;
const CONTEXT_WINDOW_INPUT_BOX_WIDTH: f32 = 120.;
pub(super) fn context_window_snap_values(min: u32, max: u32) -> Vec<f32> {
if min >= max {
return vec![min as f32];
}
let range = (max - min) as f64;
let step = nice_step(range / 8.0);
let mut values = vec![min as f32];
let mut v = (min as f64 / step).ceil() * step;
while v < max as f64 {
if v > min as f64 {
values.push(v as f32);
}
v += step;
}
if values.last().copied() != Some(max as f32) {
values.push(max as f32);
}
values
}
fn nice_step(raw: f64) -> f64 {
let magnitude = 10f64.powf(raw.log10().floor());
let normalized = raw / magnitude;
let nice = if normalized < 1.5 {
1.0
} else if normalized < 3.5 {
2.5
} else if normalized < 7.5 {
5.0
} else {
10.0
};
nice * magnitude
}
use crate::settings_view::{render_input_list, render_separator, InputListItem};
@@ -90,7 +131,7 @@ pub fn render_section_label(label: &str, appearance: &Appearance) -> Box<dyn Ele
.finish()
}
fn render_filterable_dropdown_row<T: Clone + 'static + std::fmt::Debug + Send + Sync>(
fn render_filterable_dropdown_row<T: DropdownItemAction>(
appearance: &Appearance,
label: &str,
desc: &str,
@@ -161,8 +202,14 @@ fn render_info_section(
.finish();
Container::new(description).with_margin_bottom(12.).finish()
}
fn render_long_context_pricing_warning(appearance: &Appearance) -> Box<dyn Element> {
render_warning_box(
WarningBoxConfig::formatted_title(long_context_pricing_warning_title()),
appearance,
)
}
fn render_permission_row<T: Clone + 'static + std::fmt::Debug + Send + Sync>(
fn render_permission_row<T: DropdownItemAction>(
appearance: &Appearance,
icon: Icon,
label: &str,
@@ -212,6 +259,7 @@ fn render_permission_row<T: Clone + 'static + std::fmt::Debug + Send + Sync>(
pub fn render_models_section(
appearance: &Appearance,
view: &ExecutionProfileEditorView,
app: &AppContext,
) -> Box<dyn Element> {
let mut column = Flex::column()
.with_child(render_separator(appearance))
@@ -221,14 +269,19 @@ pub fn render_models_section(
"Base model",
"This model serves as the primary engine behind the agent. It powers most interactions and invokes other models for tasks like planning or code generation when necessary. Warp may automatically switch to alternate models based on model availability or for auxiliary tasks such as conversation summarization.",
&view.base_model_dropdown,
))
.with_child(render_filterable_dropdown_row(
appearance,
"Full terminal use model",
"The model used when the agent operates inside interactive terminal applications like database shells, debuggers, REPLs, or dev servers—reading live output and writing commands to the PTY.",
&view.full_terminal_use_model_dropdown,
));
if let Some(row) = render_context_window_row(appearance, view, app) {
column.add_child(row);
}
column = column.with_child(render_filterable_dropdown_row(
appearance,
"Full terminal use model",
"The model used when the agent operates inside interactive terminal applications like database shells, debuggers, REPLs, or dev servers—reading live output and writing commands to the PTY.",
&view.full_terminal_use_model_dropdown,
));
if FeatureFlag::LocalComputerUse.is_enabled() {
column.add_child(render_filterable_dropdown_row(
appearance,
@@ -243,6 +296,150 @@ pub fn render_models_section(
.finish()
}
/// Renders a `[min — slider — max] [input]` row beneath the base model
/// dropdown. Returns `None` if the active base model doesn't advertise a
/// configurable context window or global AI is disabled.
fn render_context_window_row(
appearance: &Appearance,
view: &ExecutionProfileEditorView,
app: &AppContext,
) -> Option<Box<dyn Element>> {
if !AISettings::as_ref(app).is_any_ai_enabled(app) {
return None;
}
let cw = view.configurable_context_window(app)?;
let min = cw.min;
let max = cw.max;
let label = Text::new(
"Context window".to_string(),
appearance.ui_font_family(),
13.,
)
.with_color(appearance.theme().active_ui_text_color().into())
.finish();
let min_label_text = min.separate_with_commas();
let max_label_text = max.separate_with_commas();
let desc = Text::new(
"The base model's working memory — how many tokens of your conversation, code, and documents it can consider at once. Larger windows enable longer conversations and more coherent responses over bigger codebases, at the cost of higher latency and compute usage.".to_string(),
appearance.ui_font_family(),
11.,
)
.with_color(
appearance
.theme()
.sub_text_color(appearance.theme().surface_1())
.into(),
)
.finish();
let label_desc = Flex::column().with_child(label).with_child(desc).finish();
let min_label = Text::new(min_label_text.clone(), appearance.ui_font_family(), 11.)
.with_color(
appearance
.theme()
.sub_text_color(appearance.theme().surface_1())
.into(),
)
.finish();
let max_label = Text::new(max_label_text.clone(), appearance.ui_font_family(), 11.)
.with_color(
appearance
.theme()
.sub_text_color(appearance.theme().surface_1())
.into(),
)
.finish();
let current_value = view
.current_context_window_display_value(app)
.unwrap_or(cw.default_max)
.clamp(min, max);
let slider = appearance
.ui_builder()
.slider(view.context_window_slider_state.clone())
.with_range(min as f32..max as f32)
.with_snap_values(context_window_snap_values(min, max))
.with_default_value(current_value as f32)
.with_style(UiComponentStyles {
width: Some(CONTEXT_WINDOW_SLIDER_WIDTH),
margin: Some(Coords::default().left(8.).right(8.)),
..Default::default()
})
.on_drag(|ctx, _, val| {
ctx.dispatch_typed_action(
ExecutionProfileEditorViewAction::ContextWindowSliderDragged {
value: val.round() as u32,
},
);
})
.on_change(|ctx, _, val| {
ctx.dispatch_typed_action(ExecutionProfileEditorViewAction::SetContextWindowSize {
value: val.round() as u32,
});
})
.build()
.finish();
let context_window_editor = view.context_window_editor.clone();
let input_box = Dismiss::new(
appearance
.ui_builder()
.text_input(view.context_window_editor.clone())
.with_style(UiComponentStyles {
width: Some(CONTEXT_WINDOW_INPUT_BOX_WIDTH),
padding: Some(Coords {
top: 6.,
bottom: 6.,
left: 10.,
right: 10.,
}),
margin: Some(Coords::default().left(12.)),
background: Some(appearance.theme().surface_2().into()),
..Default::default()
})
.build()
.finish(),
)
.on_dismiss(move |ctx, app| {
let buffer_text = context_window_editor.as_ref(app).buffer_text(app);
let cleaned: String = buffer_text
.chars()
.filter(|c| !c.is_whitespace() && *c != ',')
.collect();
if let Ok(parsed) = cleaned.parse::<u32>() {
ctx.dispatch_typed_action(ExecutionProfileEditorViewAction::SetContextWindowSize {
value: parsed,
});
}
})
.finish();
let slider_row = Flex::row()
.with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_child(min_label)
.with_child(Shrinkable::new(1., slider).finish())
.with_child(max_label)
.with_child(input_box)
.finish();
let mut column = Flex::column()
.with_child(Container::new(label_desc).with_margin_bottom(4.).finish())
.with_child(slider_row);
if BlocklistAIPermissions::as_ref(app)
.permissions_profile_for_id(app, view.profile_id())
.should_show_long_context_pricing_warning(view.dragged_context_window_value, app)
{
column.add_child(render_long_context_pricing_warning(appearance));
}
Some(
Container::new(column.finish())
.with_margin_bottom(12.)
.finish(),
)
}
pub fn render_permissions_section(
appearance: &Appearance,
view: &ExecutionProfileEditorView,
@@ -362,6 +559,17 @@ pub fn render_permissions_section(
.ask_user_question_tooltip_mouse_state
.clone(),
));
column.add_child(render_permission_row(
appearance,
Icon::Atom,
"Run orchestrated agents",
&view.run_agents_dropdown,
profile_data.run_agents.description(),
!ai_settings.is_run_agents_permissions_editable(app),
view.tooltip_mouse_state_handles
.run_agents_tooltip_mouse_state
.clone(),
));
column.add_child(render_permission_row(
appearance,
@@ -475,10 +683,12 @@ where
item: display_fn(&item),
mouse_state_handle,
on_remove_action: on_remove_action(item),
is_disabled: !is_editable,
tooltip_mouse_state: None,
})
.collect();
let list = render_input_list(None, input_items, editor, !is_editable, appearance);
let list = render_input_list(None, input_items, editor, appearance);
let list_element = if !is_editable {
wrap_disabled_with_workspace_override_tooltip(list, tooltip_mouse_state, appearance)
} else {
@@ -558,24 +768,58 @@ fn render_command_denylist_section(
appearance: &Appearance,
app: &galaxyui::AppContext,
) -> Box<dyn Element> {
let ai_settings = AISettings::as_ref(app);
let is_editable = ai_settings.is_command_denylist_editable(app);
render_list_section(
let ai_disabled = !AISettings::as_ref(app).is_any_ai_enabled(app);
let org_denylist = BlocklistAIPermissions::get_org_execute_commands_denylist(app);
let mut tooltip_idx = 0usize;
let input_items: Vec<InputListItem<ExecutionProfileEditorViewAction>> = profile_data
.command_denylist
.iter()
.cloned()
.zip(view.command_denylist_mouse_state_handles.iter().cloned())
.rev()
.map(|(predicate, mouse_state_handle)| {
let is_org = org_denylist.contains(&predicate);
let tooltip_mouse_state = if is_org {
let handle = view
.command_denylist_tooltip_mouse_state_handles
.get(tooltip_idx)
.cloned();
tooltip_idx += 1;
handle
} else {
None
};
InputListItem {
item: predicate.to_string(),
mouse_state_handle,
on_remove_action: ExecutionProfileEditorViewAction::RemoveFromCommandDenylist {
predicate,
},
is_disabled: is_org || ai_disabled,
tooltip_mouse_state,
}
})
.collect();
let list = render_input_list(
None,
input_items,
Some(&view.command_denylist_editor),
appearance,
);
let mut column = Flex::column().with_child(create_section_header(
"Command denylist",
"Regular expressions to match commands that Oz should always ask permission to execute.",
&profile_data.command_denylist,
&view.command_denylist_mouse_state_handles,
Some(&view.command_denylist_editor),
None,
|predicate| ExecutionProfileEditorViewAction::RemoveFromCommandDenylist { predicate },
|item| item.to_string(),
appearance,
is_editable,
view.tooltip_mouse_state_handles
.command_denylist_editor_tooltip_mouse_state
.clone(),
)
));
column = column.with_child(list);
Container::new(column.finish())
.with_margin_bottom(16.)
.finish()
}
fn display_mcp_name(uuid: &Uuid, app: &AppContext) -> String {