first pass of merging in warp (doesn't build)
This commit is contained in:
@@ -1,46 +1,53 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use ai::api_keys::{ApiKeyManager, ApiKeyManagerEvent};
|
||||
use itertools::Itertools;
|
||||
use regex::Regex;
|
||||
use thousands::Separable;
|
||||
use galaxy_core::ui::theme::color::internal_colors;
|
||||
use warpui::elements::{
|
||||
Align, Border, ChildView, ClippedScrollStateHandle, ClippedScrollable, ConstrainedBox,
|
||||
Container, CrossAxisAlignment, Expanded, Flex, Highlight, MouseStateHandle, ParentElement,
|
||||
PartialClickableElement, ScrollbarWidth, Text,
|
||||
};
|
||||
use warpui::fonts::Properties;
|
||||
use warpui::platform::Cursor;
|
||||
use warpui::ui_components::slider::SliderStateHandle;
|
||||
use warpui::ui_components::switch::SwitchStateHandle;
|
||||
use warpui::{
|
||||
AppContext, Element, Entity, ModelHandle, SingletonEntity, TypedActionView, View, ViewContext,
|
||||
ViewHandle,
|
||||
};
|
||||
|
||||
use crate::ai::blocklist::BlocklistAIPermissions;
|
||||
use crate::ai::execution_profiles::model_menu_items::available_model_menu_items;
|
||||
use crate::ai::execution_profiles::profiles::{
|
||||
AIExecutionProfilesModel, AIExecutionProfilesModelEvent, ClientProfileId,
|
||||
};
|
||||
use crate::ai::execution_profiles::{
|
||||
profiles::{AIExecutionProfilesModel, AIExecutionProfilesModelEvent, ClientProfileId},
|
||||
AIExecutionProfile, ActionPermission, WriteToPtyPermission,
|
||||
AIExecutionProfile, AIExecutionProfileAppExt as _, ActionPermission, RunAgentsPermission,
|
||||
WriteToPtyPermission,
|
||||
};
|
||||
use crate::ai::llms::{
|
||||
DisableReason, LLMContextWindow, LLMId, LLMInfo, LLMPreferences, LLMPreferencesEvent,
|
||||
};
|
||||
use crate::ai::llms::{DisableReason, LLMId, LLMInfo, LLMPreferences, LLMPreferencesEvent};
|
||||
use crate::ai::paths::host_native_absolute_path;
|
||||
use crate::editor::InteractionState;
|
||||
use crate::editor::{EditorView, Event as EditorEvent, SingleLineEditorOptions};
|
||||
use crate::editor::{
|
||||
EditorView, Event as EditorEvent, InteractionState, SingleLineEditorOptions, TextOptions,
|
||||
};
|
||||
use crate::pane_group::focus_state::PaneFocusHandle;
|
||||
use crate::settings::{AISettings, AgentModeCommandExecutionPredicate};
|
||||
use crate::pane_group::pane::view;
|
||||
use crate::pane_group::{BackingView, PaneConfiguration, PaneEvent};
|
||||
use crate::settings::{AISettings, AISettingsChangedEvent, AgentModeCommandExecutionPredicate};
|
||||
use crate::ui_components::icons::Icon;
|
||||
use crate::view_components::action_button::{ActionButton, DangerSecondaryTheme};
|
||||
use crate::view_components::dropdown::DropdownAction;
|
||||
use crate::view_components::{
|
||||
action_button::{ActionButton, DangerSecondaryTheme},
|
||||
Dropdown, DropdownItem, FilterableDropdown, SubmittableTextInput, SubmittableTextInputEvent,
|
||||
};
|
||||
use crate::workspace::WorkspaceAction;
|
||||
use crate::workspaces::user_workspaces::UserWorkspacesEvent;
|
||||
use crate::TemplatableMCPServerManager;
|
||||
use crate::UserWorkspaces;
|
||||
use crate::{
|
||||
pane_group::{pane::view, BackingView, PaneConfiguration, PaneEvent},
|
||||
Appearance,
|
||||
};
|
||||
use ai::api_keys::{ApiKeyManager, ApiKeyManagerEvent};
|
||||
use galaxy_core::ui::theme::color::internal_colors;
|
||||
use galaxyui::fonts::Properties;
|
||||
use galaxyui::platform::Cursor;
|
||||
use galaxyui::ui_components::switch::SwitchStateHandle;
|
||||
use itertools::Itertools;
|
||||
use regex::Regex;
|
||||
|
||||
use galaxyui::{
|
||||
elements::{
|
||||
Align, Border, ChildView, ClippedScrollStateHandle, ClippedScrollable, ConstrainedBox,
|
||||
Container, CrossAxisAlignment, Expanded, Flex, Highlight, MouseStateHandle, ParentElement,
|
||||
PartialClickableElement, ScrollbarWidth, Text,
|
||||
},
|
||||
AppContext, Element, Entity, ModelHandle, SingletonEntity, TypedActionView, View, ViewContext,
|
||||
ViewHandle,
|
||||
};
|
||||
use std::path::{Path, PathBuf};
|
||||
use crate::{Appearance, TemplatableMCPServerManager, UserWorkspaces};
|
||||
|
||||
const MODEL_MENU_WIDTH: f32 = 480.;
|
||||
|
||||
@@ -119,10 +126,10 @@ struct TooltipMouseStateHandles {
|
||||
write_to_pty_tooltip_mouse_state: MouseStateHandle,
|
||||
computer_use_tooltip_mouse_state: MouseStateHandle,
|
||||
ask_user_question_tooltip_mouse_state: MouseStateHandle,
|
||||
run_agents_tooltip_mouse_state: MouseStateHandle,
|
||||
call_mcp_servers_tooltip_mouse_state: MouseStateHandle,
|
||||
// Separate mouse state handles for text input editors (for workspace override tooltips)
|
||||
command_allowlist_editor_tooltip_mouse_state: MouseStateHandle,
|
||||
command_denylist_editor_tooltip_mouse_state: MouseStateHandle,
|
||||
directory_allowlist_editor_tooltip_mouse_state: MouseStateHandle,
|
||||
mcp_allowlist_editor_tooltip_mouse_state: MouseStateHandle,
|
||||
mcp_denylist_editor_tooltip_mouse_state: MouseStateHandle,
|
||||
@@ -145,6 +152,15 @@ pub enum ExecutionProfileEditorViewAction {
|
||||
SetBaseModel {
|
||||
id: LLMId,
|
||||
},
|
||||
/// Fired continuously while the user drags the context window slider.
|
||||
ContextWindowSliderDragged {
|
||||
value: u32,
|
||||
},
|
||||
/// Fired when the user commits a new context window value (slider drop,
|
||||
/// track click, or input box commit).
|
||||
SetContextWindowSize {
|
||||
value: u32,
|
||||
},
|
||||
SetCodingModel {
|
||||
id: LLMId,
|
||||
},
|
||||
@@ -177,6 +193,9 @@ pub enum ExecutionProfileEditorViewAction {
|
||||
SetAskUserQuestion {
|
||||
permission: super::AskUserQuestionPermission,
|
||||
},
|
||||
SetRunAgents {
|
||||
permission: RunAgentsPermission,
|
||||
},
|
||||
AddToCommandAllowlist {
|
||||
predicate: AgentModeCommandExecutionPredicate,
|
||||
},
|
||||
@@ -222,6 +241,10 @@ pub struct ExecutionProfileEditorView {
|
||||
focus_handle: Option<PaneFocusHandle>,
|
||||
clipped_scroll_state: ClippedScrollStateHandle,
|
||||
base_model_dropdown: ViewHandle<FilterableDropdown<ExecutionProfileEditorViewAction>>,
|
||||
context_window_slider_state: SliderStateHandle,
|
||||
context_window_editor: ViewHandle<EditorView>,
|
||||
last_synced_context_window_editor_value: Option<u32>,
|
||||
dragged_context_window_value: Option<u32>,
|
||||
coding_model_dropdown: ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
|
||||
full_terminal_use_model_dropdown:
|
||||
ViewHandle<FilterableDropdown<ExecutionProfileEditorViewAction>>,
|
||||
@@ -233,11 +256,13 @@ pub struct ExecutionProfileEditorView {
|
||||
call_mcp_servers_dropdown: ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
|
||||
computer_use_dropdown: ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
|
||||
ask_user_question_dropdown: ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
|
||||
run_agents_dropdown: ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
|
||||
command_allowlist_editor: ViewHandle<SubmittableTextInput>,
|
||||
command_denylist_editor: ViewHandle<SubmittableTextInput>,
|
||||
directory_allowlist_editor: ViewHandle<SubmittableTextInput>,
|
||||
command_allowlist_mouse_state_handles: Vec<MouseStateHandle>,
|
||||
command_denylist_mouse_state_handles: Vec<MouseStateHandle>,
|
||||
command_denylist_tooltip_mouse_state_handles: Vec<MouseStateHandle>,
|
||||
directory_allowlist_mouse_state_handles: Vec<MouseStateHandle>,
|
||||
mcp_allowlist_dropdown: ViewHandle<FilterableDropdown<ExecutionProfileEditorViewAction>>,
|
||||
mcp_allowlist_mouse_state_handles: Vec<MouseStateHandle>,
|
||||
@@ -451,6 +476,34 @@ impl ExecutionProfileEditorView {
|
||||
dropdown
|
||||
});
|
||||
|
||||
let run_agents_dropdown = ctx.add_typed_action_view(|ctx| {
|
||||
let mut dropdown = Dropdown::new(ctx);
|
||||
dropdown.set_items(
|
||||
vec![
|
||||
DropdownItem::new(
|
||||
"Never",
|
||||
ExecutionProfileEditorViewAction::SetRunAgents {
|
||||
permission: RunAgentsPermission::NeverAllow,
|
||||
},
|
||||
),
|
||||
DropdownItem::new(
|
||||
"Always allow",
|
||||
ExecutionProfileEditorViewAction::SetRunAgents {
|
||||
permission: RunAgentsPermission::AlwaysAllow,
|
||||
},
|
||||
),
|
||||
DropdownItem::new(
|
||||
"Always ask",
|
||||
ExecutionProfileEditorViewAction::SetRunAgents {
|
||||
permission: RunAgentsPermission::AlwaysAsk,
|
||||
},
|
||||
),
|
||||
],
|
||||
ctx,
|
||||
);
|
||||
dropdown
|
||||
});
|
||||
|
||||
let mcp_allowlist_dropdown = ctx.add_typed_action_view(|ctx| {
|
||||
let mut dropdown = FilterableDropdown::new(ctx);
|
||||
dropdown.set_menu_header_to_static("Select MCP servers");
|
||||
@@ -484,6 +537,27 @@ impl ExecutionProfileEditorView {
|
||||
dropdown.set_menu_width(MODEL_MENU_WIDTH, ctx);
|
||||
dropdown
|
||||
});
|
||||
|
||||
// Initialize the context window editor buffer with the profile's
|
||||
// persisted limit (or the active model's max as a sensible default).
|
||||
// The slider's current position is derived from the profile on each
|
||||
// render, so no local Cell is needed.
|
||||
let initial_context_window_value = initial_context_window_display_value(&profile_data, ctx);
|
||||
let context_window_slider_state = SliderStateHandle::default();
|
||||
let context_window_editor = ctx.add_typed_action_view(|ctx| {
|
||||
let options = SingleLineEditorOptions {
|
||||
text: TextOptions {
|
||||
font_size_override: Some(Appearance::as_ref(ctx).ui_font_size()),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
let mut editor = EditorView::single_line(options, ctx);
|
||||
editor.set_buffer_text(&initial_context_window_value.separate_with_commas(), ctx);
|
||||
editor
|
||||
});
|
||||
let last_synced_context_window_editor_value = Some(initial_context_window_value);
|
||||
|
||||
let coding_model_dropdown = ctx.add_typed_action_view(|ctx| {
|
||||
let mut dropdown = Dropdown::new(ctx);
|
||||
dropdown.set_top_bar_max_width(MODEL_MENU_WIDTH);
|
||||
@@ -578,6 +652,10 @@ impl ExecutionProfileEditorView {
|
||||
focus_handle: None,
|
||||
clipped_scroll_state: Default::default(),
|
||||
base_model_dropdown,
|
||||
context_window_slider_state,
|
||||
context_window_editor,
|
||||
last_synced_context_window_editor_value,
|
||||
dragged_context_window_value: None,
|
||||
coding_model_dropdown,
|
||||
full_terminal_use_model_dropdown,
|
||||
computer_use_model_dropdown,
|
||||
@@ -588,11 +666,17 @@ impl ExecutionProfileEditorView {
|
||||
call_mcp_servers_dropdown,
|
||||
computer_use_dropdown,
|
||||
ask_user_question_dropdown,
|
||||
run_agents_dropdown,
|
||||
command_allowlist_editor,
|
||||
command_denylist_editor,
|
||||
directory_allowlist_editor,
|
||||
command_allowlist_mouse_state_handles,
|
||||
command_denylist_mouse_state_handles,
|
||||
command_denylist_tooltip_mouse_state_handles: profile_data
|
||||
.command_denylist
|
||||
.iter()
|
||||
.map(|_| Default::default())
|
||||
.collect(),
|
||||
directory_allowlist_mouse_state_handles,
|
||||
mcp_allowlist_dropdown,
|
||||
mcp_allowlist_mouse_state_handles,
|
||||
@@ -612,6 +696,10 @@ impl ExecutionProfileEditorView {
|
||||
}
|
||||
});
|
||||
|
||||
ctx.subscribe_to_view(&view.context_window_editor, |view, _, event, ctx| {
|
||||
view.handle_context_window_editor_event(event, ctx);
|
||||
});
|
||||
|
||||
ctx.subscribe_to_view(&view.command_allowlist_editor, |view, _, event, ctx| {
|
||||
if let SubmittableTextInputEvent::Submit(s) = event {
|
||||
let predicate = match AgentModeCommandExecutionPredicate::new_regex(s) {
|
||||
@@ -671,7 +759,7 @@ impl ExecutionProfileEditorView {
|
||||
Self::refresh_filterable_model_dropdown(
|
||||
&me.base_model_dropdown,
|
||||
current_permissions.base_model.clone(),
|
||||
|prefs| prefs.get_base_llm_choices_for_agent_mode().collect_vec(),
|
||||
|prefs, app| prefs.get_base_llm_choices_for_agent_mode(app).collect_vec(),
|
||||
|id| ExecutionProfileEditorViewAction::SetBaseModel { id },
|
||||
|prefs| prefs.get_default_base_model().id.clone(),
|
||||
&me.upgrade_footer_mouse_state,
|
||||
@@ -685,7 +773,7 @@ impl ExecutionProfileEditorView {
|
||||
Self::refresh_filterable_model_dropdown(
|
||||
&me.full_terminal_use_model_dropdown,
|
||||
current_permissions.cli_agent_model.clone(),
|
||||
|prefs| prefs.get_cli_agent_llm_choices().collect_vec(),
|
||||
|prefs, app| prefs.get_cli_agent_llm_choices(app).collect_vec(),
|
||||
|id| ExecutionProfileEditorViewAction::SetFullTerminalUseModel { id },
|
||||
|prefs| prefs.get_default_cli_agent_model().id.clone(),
|
||||
&me.upgrade_footer_mouse_state,
|
||||
@@ -694,23 +782,25 @@ impl ExecutionProfileEditorView {
|
||||
Self::refresh_filterable_model_dropdown(
|
||||
&me.computer_use_model_dropdown,
|
||||
current_permissions.computer_use_model.clone(),
|
||||
|prefs| prefs.get_computer_use_llm_choices().collect_vec(),
|
||||
|prefs, _| prefs.get_computer_use_llm_choices().collect_vec(),
|
||||
|id| ExecutionProfileEditorViewAction::SetComputerUseModel { id },
|
||||
|prefs| prefs.get_default_computer_use_model().id.clone(),
|
||||
&me.upgrade_footer_mouse_state,
|
||||
ctx,
|
||||
);
|
||||
me.sync_context_window_editor(ctx, false);
|
||||
}
|
||||
LLMPreferencesEvent::UpdatedActiveAgentModeLLM => {
|
||||
Self::refresh_filterable_model_dropdown(
|
||||
&me.base_model_dropdown,
|
||||
current_permissions.base_model.clone(),
|
||||
|prefs| prefs.get_base_llm_choices_for_agent_mode().collect_vec(),
|
||||
|prefs, app| prefs.get_base_llm_choices_for_agent_mode(app).collect_vec(),
|
||||
|id| ExecutionProfileEditorViewAction::SetBaseModel { id },
|
||||
|prefs| prefs.get_default_base_model().id.clone(),
|
||||
&me.upgrade_footer_mouse_state,
|
||||
ctx,
|
||||
);
|
||||
me.sync_context_window_editor(ctx, false);
|
||||
}
|
||||
LLMPreferencesEvent::UpdatedActiveCodingLLM => {
|
||||
Self::refresh_coding_model_dropdown(
|
||||
@@ -732,7 +822,7 @@ impl ExecutionProfileEditorView {
|
||||
Self::refresh_filterable_model_dropdown(
|
||||
&me.base_model_dropdown,
|
||||
current_permissions.base_model.clone(),
|
||||
|prefs| prefs.get_base_llm_choices_for_agent_mode().collect_vec(),
|
||||
|prefs, app| prefs.get_base_llm_choices_for_agent_mode(app).collect_vec(),
|
||||
|id| ExecutionProfileEditorViewAction::SetBaseModel { id },
|
||||
|prefs| prefs.get_default_base_model().id.clone(),
|
||||
&me.upgrade_footer_mouse_state,
|
||||
@@ -743,6 +833,7 @@ impl ExecutionProfileEditorView {
|
||||
current_permissions.coding_model.clone(),
|
||||
ctx,
|
||||
);
|
||||
me.sync_context_window_editor(ctx, false);
|
||||
ctx.notify();
|
||||
},
|
||||
);
|
||||
@@ -761,6 +852,15 @@ impl ExecutionProfileEditorView {
|
||||
ctx.subscribe_to_model(&workspace, |me, workspace, event, ctx| {
|
||||
if let UserWorkspacesEvent::TeamsChanged = event {
|
||||
Self::update_all_editor_interaction_states(me, workspace, ctx);
|
||||
me.update_mouse_state_handles(ctx);
|
||||
ctx.notify();
|
||||
}
|
||||
});
|
||||
ctx.subscribe_to_model(&AISettings::handle(ctx), |me, _, event, ctx| {
|
||||
if let AISettingsChangedEvent::IsAnyAIEnabled { .. } = event {
|
||||
let workspace = UserWorkspaces::handle(ctx);
|
||||
Self::update_all_editor_interaction_states(me, workspace, ctx);
|
||||
me.sync_context_window_editor(ctx, true);
|
||||
ctx.notify();
|
||||
}
|
||||
});
|
||||
@@ -795,6 +895,12 @@ impl ExecutionProfileEditorView {
|
||||
.map(|_| Default::default())
|
||||
.collect();
|
||||
|
||||
self.command_denylist_tooltip_mouse_state_handles = current_permissions
|
||||
.command_denylist
|
||||
.iter()
|
||||
.map(|_| Default::default())
|
||||
.collect();
|
||||
|
||||
self.directory_allowlist_mouse_state_handles = current_permissions
|
||||
.directory_allowlist
|
||||
.iter()
|
||||
@@ -826,12 +932,13 @@ impl ExecutionProfileEditorView {
|
||||
let computer_use_disabled = !ai_settings.is_computer_use_permissions_editable(ctx);
|
||||
let ask_user_question_disabled =
|
||||
!ai_settings.is_ask_user_question_permissions_editable(ctx);
|
||||
let run_agents_disabled = !ai_settings.is_run_agents_permissions_editable(ctx);
|
||||
let mcp_disabled = !ai_settings.is_mcp_permission_editable(ctx);
|
||||
|
||||
Self::refresh_filterable_model_dropdown(
|
||||
&self.base_model_dropdown,
|
||||
current_permissions.base_model.clone(),
|
||||
|prefs| prefs.get_base_llm_choices_for_agent_mode().collect_vec(),
|
||||
|prefs, app| prefs.get_base_llm_choices_for_agent_mode(app).collect_vec(),
|
||||
|id| ExecutionProfileEditorViewAction::SetBaseModel { id },
|
||||
|prefs| prefs.get_default_base_model().id.clone(),
|
||||
&self.upgrade_footer_mouse_state,
|
||||
@@ -845,7 +952,7 @@ impl ExecutionProfileEditorView {
|
||||
Self::refresh_filterable_model_dropdown(
|
||||
&self.full_terminal_use_model_dropdown,
|
||||
current_permissions.cli_agent_model.clone(),
|
||||
|prefs| prefs.get_cli_agent_llm_choices().collect_vec(),
|
||||
|prefs, app| prefs.get_cli_agent_llm_choices(app).collect_vec(),
|
||||
|id| ExecutionProfileEditorViewAction::SetFullTerminalUseModel { id },
|
||||
|prefs| prefs.get_default_cli_agent_model().id.clone(),
|
||||
&self.upgrade_footer_mouse_state,
|
||||
@@ -854,7 +961,7 @@ impl ExecutionProfileEditorView {
|
||||
Self::refresh_filterable_model_dropdown(
|
||||
&self.computer_use_model_dropdown,
|
||||
current_permissions.computer_use_model.clone(),
|
||||
|prefs| prefs.get_computer_use_llm_choices().collect_vec(),
|
||||
|prefs, _| prefs.get_computer_use_llm_choices().collect_vec(),
|
||||
|id| ExecutionProfileEditorViewAction::SetComputerUseModel { id },
|
||||
|prefs| prefs.get_default_computer_use_model().id.clone(),
|
||||
&self.upgrade_footer_mouse_state,
|
||||
@@ -903,6 +1010,12 @@ impl ExecutionProfileEditorView {
|
||||
ask_user_question_disabled,
|
||||
ctx,
|
||||
);
|
||||
Self::refresh_run_agents_dropdown_menu(
|
||||
&self.run_agents_dropdown,
|
||||
current_permissions.run_agents,
|
||||
run_agents_disabled,
|
||||
ctx,
|
||||
);
|
||||
Self::refresh_mcp_dropdown(
|
||||
&self.mcp_allowlist_dropdown,
|
||||
|uuid| ExecutionProfileEditorViewAction::AddToMCPAllowlist { id: uuid },
|
||||
@@ -919,6 +1032,7 @@ impl ExecutionProfileEditorView {
|
||||
);
|
||||
|
||||
Self::update_profile_name_editor(&self.profile_name_editor, ¤t_permissions, ctx);
|
||||
self.sync_context_window_editor(ctx, false);
|
||||
}
|
||||
|
||||
fn refresh_execution_profile_dropdown_menu(
|
||||
@@ -1022,6 +1136,31 @@ impl ExecutionProfileEditorView {
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
fn refresh_run_agents_dropdown_menu(
|
||||
menu: &ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
|
||||
current_permission: RunAgentsPermission,
|
||||
disabled: bool,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) {
|
||||
menu.update(ctx, |menu, ctx| {
|
||||
if !disabled {
|
||||
menu.set_enabled(ctx);
|
||||
} else {
|
||||
menu.set_disabled(ctx);
|
||||
}
|
||||
|
||||
let active = match current_permission {
|
||||
RunAgentsPermission::NeverAllow | RunAgentsPermission::Unknown => 0,
|
||||
RunAgentsPermission::AlwaysAllow => 1,
|
||||
RunAgentsPermission::AlwaysAsk => 2,
|
||||
};
|
||||
|
||||
menu.set_selected_by_index(active, ctx);
|
||||
ctx.notify();
|
||||
});
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
fn refresh_filterable_model_dropdown<G, A, D>(
|
||||
menu: &ViewHandle<FilterableDropdown<ExecutionProfileEditorViewAction>>,
|
||||
profile_model: Option<LLMId>,
|
||||
@@ -1031,7 +1170,7 @@ impl ExecutionProfileEditorView {
|
||||
upgrade_mouse_state: &MouseStateHandle,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) where
|
||||
G: FnOnce(&LLMPreferences) -> Vec<&LLMInfo>,
|
||||
G: for<'a> FnOnce(&'a LLMPreferences, &AppContext) -> Vec<&'a LLMInfo>,
|
||||
A: Fn(LLMId) -> ExecutionProfileEditorViewAction,
|
||||
D: FnOnce(&LLMPreferences) -> LLMId,
|
||||
{
|
||||
@@ -1046,7 +1185,7 @@ impl ExecutionProfileEditorView {
|
||||
|
||||
let llm_prefs = LLMPreferences::handle(ctx);
|
||||
let llm_prefs = llm_prefs.as_ref(ctx);
|
||||
let choices = get_choices(llm_prefs);
|
||||
let choices = get_choices(llm_prefs, ctx);
|
||||
|
||||
let has_upgrade_gated_models = choices
|
||||
.iter()
|
||||
@@ -1054,7 +1193,7 @@ impl ExecutionProfileEditorView {
|
||||
|
||||
let items = available_model_menu_items(
|
||||
choices,
|
||||
|llm| create_action(llm.id.clone()).into(),
|
||||
|llm| DropdownAction::select_action_and_close(create_action(llm.id.clone())),
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
@@ -1097,13 +1236,15 @@ impl ExecutionProfileEditorView {
|
||||
}
|
||||
|
||||
let choices = LLMPreferences::as_ref(ctx)
|
||||
.get_coding_llm_choices()
|
||||
.get_coding_llm_choices(ctx)
|
||||
.collect_vec();
|
||||
|
||||
let items = available_model_menu_items(
|
||||
choices,
|
||||
|llm| {
|
||||
ExecutionProfileEditorViewAction::SetCodingModel { id: llm.id.clone() }.into()
|
||||
DropdownAction::select_action_and_close(
|
||||
ExecutionProfileEditorViewAction::SetCodingModel { id: llm.id.clone() },
|
||||
)
|
||||
},
|
||||
None,
|
||||
None,
|
||||
@@ -1225,7 +1366,7 @@ impl ExecutionProfileEditorView {
|
||||
|
||||
Self::update_editor_interaction_state(
|
||||
view.command_denylist_editor.as_ref(ctx).editor().clone(),
|
||||
is_any_ai_enabled && !ai_autonomy_settings.has_override_for_execute_commands_denylist(),
|
||||
is_any_ai_enabled,
|
||||
ctx,
|
||||
);
|
||||
|
||||
@@ -1256,10 +1397,112 @@ impl ExecutionProfileEditorView {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn configurable_context_window(&self, app: &AppContext) -> Option<LLMContextWindow> {
|
||||
let profile =
|
||||
BlocklistAIPermissions::as_ref(app).permissions_profile_for_id(app, self.profile_id);
|
||||
profile.configurable_context_window(app)
|
||||
}
|
||||
|
||||
fn current_context_window_display_value(&self, app: &AppContext) -> Option<u32> {
|
||||
let profile =
|
||||
BlocklistAIPermissions::as_ref(app).permissions_profile_for_id(app, self.profile_id);
|
||||
profile.context_window_display_value(app)
|
||||
}
|
||||
|
||||
fn handle_context_window_editor_event(
|
||||
&mut self,
|
||||
event: &EditorEvent,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
EditorEvent::Blurred | EditorEvent::Enter => {
|
||||
if !AISettings::as_ref(ctx).is_any_ai_enabled(ctx) {
|
||||
self.sync_context_window_editor(ctx, true);
|
||||
return;
|
||||
}
|
||||
let Some(cw) = self.configurable_context_window(ctx) else {
|
||||
return;
|
||||
};
|
||||
let buffer_text = self.context_window_editor.as_ref(ctx).buffer_text(ctx);
|
||||
let cleaned: String = buffer_text
|
||||
.chars()
|
||||
.filter(|c| !c.is_whitespace() && *c != ',')
|
||||
.collect();
|
||||
if let Ok(parsed) = cleaned.parse::<u32>() {
|
||||
let clamped = parsed.clamp(cw.min, cw.max);
|
||||
if Some(clamped) != self.current_context_window_display_value(ctx) {
|
||||
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
|
||||
profiles_model.set_context_window_limit(
|
||||
self.profile_id,
|
||||
Some(clamped),
|
||||
ctx,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
self.sync_context_window_editor(ctx, true);
|
||||
ctx.notify();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_context_window_editor(&mut self, ctx: &mut ViewContext<Self>, force: bool) {
|
||||
self.dragged_context_window_value = None;
|
||||
let Some(value) = self.current_context_window_display_value(ctx) else {
|
||||
self.last_synced_context_window_editor_value = None;
|
||||
self.context_window_slider_state.reset_offset();
|
||||
ctx.notify();
|
||||
return;
|
||||
};
|
||||
|
||||
let formatted = value.separate_with_commas();
|
||||
let should_update = if force {
|
||||
true
|
||||
} else {
|
||||
match self.last_synced_context_window_editor_value {
|
||||
Some(last_value) => {
|
||||
self.context_window_editor.as_ref(ctx).buffer_text(ctx)
|
||||
== last_value.separate_with_commas()
|
||||
}
|
||||
None => true,
|
||||
}
|
||||
};
|
||||
|
||||
if should_update {
|
||||
self.context_window_editor.update(ctx, |editor, ctx| {
|
||||
if editor.buffer_text(ctx) != formatted {
|
||||
editor.system_reset_buffer_text(&formatted, ctx);
|
||||
}
|
||||
});
|
||||
self.last_synced_context_window_editor_value = Some(value);
|
||||
self.context_window_slider_state.reset_offset();
|
||||
ctx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn initial_context_window_display_value(
|
||||
profile_data: &AIExecutionProfile,
|
||||
app: &AppContext,
|
||||
) -> u32 {
|
||||
profile_data
|
||||
.context_window_display_value(app)
|
||||
.unwrap_or_else(|| {
|
||||
LLMPreferences::as_ref(app)
|
||||
.get_default_base_model()
|
||||
.context_window
|
||||
.default_max
|
||||
})
|
||||
}
|
||||
|
||||
mod ui_helpers;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "mod_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
impl View for ExecutionProfileEditorView {
|
||||
fn ui_name() -> &'static str {
|
||||
"ExecutionProfileEditorView"
|
||||
@@ -1278,7 +1521,7 @@ impl View for ExecutionProfileEditorView {
|
||||
&self.profile_name_editor,
|
||||
profile_data.is_default_profile,
|
||||
))
|
||||
.with_child(render_models_section(appearance, self))
|
||||
.with_child(render_models_section(appearance, self, app))
|
||||
.with_child(render_permissions_section(
|
||||
appearance,
|
||||
self,
|
||||
@@ -1323,9 +1566,48 @@ impl TypedActionView for ExecutionProfileEditorView {
|
||||
ctx.emit(ExecutionProfileEditorViewEvent::Pane(PaneEvent::Close));
|
||||
}
|
||||
ExecutionProfileEditorViewAction::SetBaseModel { id } => {
|
||||
// Changing the base model resets any persisted context window
|
||||
// override — the new model may have a different range (or not
|
||||
// be configurable at all). The user can pick a new value for
|
||||
// the new model if they want one.
|
||||
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
|
||||
profiles_model.set_base_model(self.profile_id, Some(id.clone()), ctx);
|
||||
profiles_model.set_context_window_limit(self.profile_id, None, ctx);
|
||||
});
|
||||
self.sync_context_window_editor(ctx, true);
|
||||
ctx.notify();
|
||||
}
|
||||
ExecutionProfileEditorViewAction::ContextWindowSliderDragged { value } => {
|
||||
if !AISettings::as_ref(ctx).is_any_ai_enabled(ctx) {
|
||||
self.sync_context_window_editor(ctx, true);
|
||||
return;
|
||||
}
|
||||
// Transient drag update: reflect the current slider position
|
||||
// in the input box without persisting to the profile yet.
|
||||
// Persistence happens on SetContextWindowSize (drop / commit).
|
||||
if self.configurable_context_window(ctx).is_some() {
|
||||
self.dragged_context_window_value = Some(*value);
|
||||
let formatted = value.separate_with_commas();
|
||||
self.context_window_editor.update(ctx, |editor, ctx| {
|
||||
editor.system_reset_buffer_text(&formatted, ctx);
|
||||
});
|
||||
ctx.notify();
|
||||
}
|
||||
}
|
||||
ExecutionProfileEditorViewAction::SetContextWindowSize { value } => {
|
||||
self.dragged_context_window_value = None;
|
||||
if !AISettings::as_ref(ctx).is_any_ai_enabled(ctx) {
|
||||
self.sync_context_window_editor(ctx, true);
|
||||
return;
|
||||
}
|
||||
let Some(cw) = self.configurable_context_window(ctx) else {
|
||||
return;
|
||||
};
|
||||
let clamped = (*value).clamp(cw.min, cw.max);
|
||||
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
|
||||
profiles_model.set_context_window_limit(self.profile_id, Some(clamped), ctx);
|
||||
});
|
||||
self.sync_context_window_editor(ctx, true);
|
||||
ctx.notify();
|
||||
}
|
||||
ExecutionProfileEditorViewAction::SetCodingModel { id } => {
|
||||
@@ -1388,6 +1670,12 @@ impl TypedActionView for ExecutionProfileEditorView {
|
||||
});
|
||||
ctx.notify();
|
||||
}
|
||||
ExecutionProfileEditorViewAction::SetRunAgents { permission } => {
|
||||
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
|
||||
profiles_model.set_run_agents(self.profile_id, *permission, ctx);
|
||||
});
|
||||
ctx.notify();
|
||||
}
|
||||
ExecutionProfileEditorViewAction::AddToCommandAllowlist { predicate } => {
|
||||
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
|
||||
profiles_model.add_to_command_allowlist(self.profile_id, predicate, ctx);
|
||||
|
||||
Reference in New Issue
Block a user