From e7b19ab0b1c6823384b4a294304d63c7ac47e93a Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Mon, 13 Oct 2025 12:19:17 -0300 Subject: [PATCH] settings_ui: Add some AI settings (#40111) There's a lot of AI settings that will require custom UI for them to be part of the settings window, but many don't (simple booleans and dropdown) and can be moved right away. In consequence, the whole "General Settings" section in the agent panel's settings view can be removed given all of those items are now part of the settings window. Release Notes: - N/A --- crates/agent_ui/src/agent_configuration.rs | 101 +------ crates/settings/src/settings_content/agent.rs | 14 +- crates/settings_ui/src/page_data.rs | 266 ++++++++++++++++++ crates/settings_ui/src/settings_ui.rs | 3 +- 4 files changed, 283 insertions(+), 101 deletions(-) diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs index b447617c340dedb6795c0c950f08eee8f0c6d59e..386eeaca1924ace86e4138fdfc283bfe0c20fae0 100644 --- a/crates/agent_ui/src/agent_configuration.rs +++ b/crates/agent_ui/src/agent_configuration.rs @@ -6,7 +6,6 @@ mod tool_picker; use std::{ops::Range, sync::Arc}; -use agent_settings::AgentSettings; use anyhow::Result; use assistant_tool::{ToolSource, ToolWorkingSet}; use cloud_llm_client::{Plan, PlanV1, PlanV2}; @@ -29,10 +28,10 @@ use project::{ agent_server_store::{AgentServerStore, CLAUDE_CODE_NAME, CODEX_NAME, GEMINI_NAME}, context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore}, }; -use settings::{Settings, SettingsStore, update_settings_file}; +use settings::{SettingsStore, update_settings_file}; use ui::{ Chip, CommonAnimationExt, ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex, - Indicator, PopoverMenu, Switch, SwitchColor, SwitchField, Tooltip, WithScrollbar, prelude::*, + Indicator, PopoverMenu, Switch, SwitchColor, Tooltip, WithScrollbar, prelude::*, }; use util::ResultExt as _; use workspace::{Workspace, create_and_open_local_file}; @@ -402,101 +401,6 @@ impl AgentConfiguration { ) } - fn render_command_permission(&mut self, cx: &mut Context) -> impl IntoElement { - let always_allow_tool_actions = AgentSettings::get_global(cx).always_allow_tool_actions; - let fs = self.fs.clone(); - - SwitchField::new( - "always-allow-tool-actions-switch", - Some("Allow running commands without asking for confirmation"), - Some( - "The agent can perform potentially destructive actions without asking for your confirmation.".into(), - ), - always_allow_tool_actions, - move |state, _window, cx| { - let allow = state == &ToggleState::Selected; - update_settings_file(fs.clone(), cx, move |settings, _| { - settings.agent.get_or_insert_default().set_always_allow_tool_actions(allow); - }); - }, - ) - } - - fn render_single_file_review(&mut self, cx: &mut Context) -> impl IntoElement { - let single_file_review = AgentSettings::get_global(cx).single_file_review; - let fs = self.fs.clone(); - - SwitchField::new( - "single-file-review", - Some("Enable single-file agent reviews"), - Some("Agent edits are also displayed in single-file editors for review.".into()), - single_file_review, - move |state, _window, cx| { - let allow = state == &ToggleState::Selected; - update_settings_file(fs.clone(), cx, move |settings, _| { - settings - .agent - .get_or_insert_default() - .set_single_file_review(allow); - }); - }, - ) - } - - fn render_sound_notification(&mut self, cx: &mut Context) -> impl IntoElement { - let play_sound_when_agent_done = AgentSettings::get_global(cx).play_sound_when_agent_done; - let fs = self.fs.clone(); - - SwitchField::new( - "sound-notification", - Some("Play sound when finished generating"), - Some( - "Hear a notification sound when the agent is done generating changes or needs your input.".into(), - ), - play_sound_when_agent_done, - move |state, _window, cx| { - let allow = state == &ToggleState::Selected; - update_settings_file(fs.clone(), cx, move |settings, _| { - settings.agent.get_or_insert_default().set_play_sound_when_agent_done(allow); - }); - }, - ) - } - - fn render_modifier_to_send(&mut self, cx: &mut Context) -> impl IntoElement { - let use_modifier_to_send = AgentSettings::get_global(cx).use_modifier_to_send; - let fs = self.fs.clone(); - - SwitchField::new( - "modifier-send", - Some("Use modifier to submit a message"), - Some( - "Make a modifier (cmd-enter on macOS, ctrl-enter on Linux or Windows) required to send messages.".into(), - ), - use_modifier_to_send, - move |state, _window, cx| { - let allow = state == &ToggleState::Selected; - update_settings_file(fs.clone(), cx, move |settings, _| { - settings.agent.get_or_insert_default().set_use_modifier_to_send(allow); - }); - }, - ) - } - - fn render_general_settings_section(&mut self, cx: &mut Context) -> impl IntoElement { - v_flex() - .p(DynamicSpacing::Base16.rems(cx)) - .pr(DynamicSpacing::Base20.rems(cx)) - .gap_2p5() - .border_b_1() - .border_color(cx.theme().colors().border) - .child(Headline::new("General Settings")) - .child(self.render_command_permission(cx)) - .child(self.render_single_file_review(cx)) - .child(self.render_sound_notification(cx)) - .child(self.render_modifier_to_send(cx)) - } - fn render_zed_plan_info(&self, plan: Option, cx: &mut Context) -> impl IntoElement { if let Some(plan) = plan { let free_chip_bg = cx @@ -1141,7 +1045,6 @@ impl Render for AgentConfiguration { .track_scroll(&self.scroll_handle) .size_full() .overflow_y_scroll() - .child(self.render_general_settings_section(cx)) .child(self.render_agent_servers_section(cx)) .child(self.render_context_servers_section(window, cx)) .child(self.render_provider_configuration_section(cx)), diff --git a/crates/settings/src/settings_content/agent.rs b/crates/settings/src/settings_content/agent.rs index 9644cbb3bd455f42052d0c4c45d958d9a492d712..80326c84688697ab4dcbc83d9013cf3acab26d2b 100644 --- a/crates/settings/src/settings_content/agent.rs +++ b/crates/settings/src/settings_content/agent.rs @@ -194,7 +194,19 @@ pub enum DefaultAgentView { TextThread, } -#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] +#[derive( + Copy, + Clone, + Default, + Debug, + Serialize, + Deserialize, + JsonSchema, + MergeFrom, + PartialEq, + strum::VariantArray, + strum::VariantNames, +)] #[serde(rename_all = "snake_case")] pub enum NotifyWhenAgentWaiting { #[default] diff --git a/crates/settings_ui/src/page_data.rs b/crates/settings_ui/src/page_data.rs index 59eacdc64301ddfab39d13040934d696bbcf7e58..912f5e18b8e7c9b44e0c22bbc087639f4d7a4361 100644 --- a/crates/settings_ui/src/page_data.rs +++ b/crates/settings_ui/src/page_data.rs @@ -3122,6 +3122,82 @@ pub(crate) fn settings_data() -> Vec { metadata: None, files: USER, }), + SettingsPageItem::SectionHeader("Agent Panel"), + SettingsPageItem::SettingItem(SettingItem { + title: "Agent Panel Button", + description: "Whether to show the agent panel button in the status bar", + field: Box::new(SettingField { + pick: |settings_content| { + if let Some(agent) = &settings_content.agent { + &agent.button + } else { + &None + } + }, + pick_mut: |settings_content| { + &mut settings_content.agent.get_or_insert_default().button + }, + }), + metadata: None, + files: USER, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Agent Panel Dock", + description: "Where to dock the agent panel.", + field: Box::new(SettingField { + pick: |settings_content| { + if let Some(agent) = &settings_content.agent { + &agent.dock + } else { + &None + } + }, + pick_mut: |settings_content| { + &mut settings_content.agent.get_or_insert_default().dock + }, + }), + metadata: None, + files: USER, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Agent Panel Default Width", + description: "Default width when the agent panel is docked to the left or right", + field: Box::new(SettingField { + pick: |settings_content| { + if let Some(agent) = &settings_content.agent { + &agent.default_width + } else { + &None + } + }, + pick_mut: |settings_content| { + &mut settings_content.agent.get_or_insert_default().default_width + }, + }), + metadata: None, + files: USER, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Agent Panel Default Height", + description: "Default height when the agent panel is docked to the bottom", + field: Box::new(SettingField { + pick: |settings_content| { + if let Some(agent) = &settings_content.agent { + &agent.default_height + } else { + &None + } + }, + pick_mut: |settings_content| { + &mut settings_content + .agent + .get_or_insert_default() + .default_height + }, + }), + metadata: None, + files: USER, + }), ], }, SettingsPage { @@ -4357,6 +4433,196 @@ pub(crate) fn settings_data() -> Vec { metadata: None, files: USER, }), + SettingsPageItem::SectionHeader("Agent Configuration"), + SettingsPageItem::SettingItem(SettingItem { + title: "Always Allow Tool Actions", + description: "When enabled, the agent can run potentially destructive actions without asking for your confirmation. This setting has no effect on external agents.", + field: Box::new(SettingField { + pick: |settings_content| { + if let Some(agent) = &settings_content.agent { + &agent.always_allow_tool_actions + } else { + &None + } + }, + pick_mut: |settings_content| { + &mut settings_content + .agent + .get_or_insert_default() + .always_allow_tool_actions + }, + }), + metadata: None, + files: USER, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Single File Review", + description: "When enabled, agent edits will also be displayed in single-file buffers for review", + field: Box::new(SettingField { + pick: |settings_content| { + if let Some(agent) = &settings_content.agent { + &agent.single_file_review + } else { + &None + } + }, + pick_mut: |settings_content| { + &mut settings_content + .agent + .get_or_insert_default() + .single_file_review + }, + }), + metadata: None, + files: USER, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Enable Feedback", + description: "Show voting thumbs up/down icon buttons for feedback on agent edits", + field: Box::new(SettingField { + pick: |settings_content| { + if let Some(agent) = &settings_content.agent { + &agent.enable_feedback + } else { + &None + } + }, + pick_mut: |settings_content| { + &mut settings_content + .agent + .get_or_insert_default() + .enable_feedback + }, + }), + metadata: None, + files: USER, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Notify When Agent Waiting", + description: "Where to show notifications when the agent has completed its response or needs confirmation before running a tool action", + field: Box::new(SettingField { + pick: |settings_content| { + if let Some(agent) = &settings_content.agent { + &agent.notify_when_agent_waiting + } else { + &None + } + }, + pick_mut: |settings_content| { + &mut settings_content + .agent + .get_or_insert_default() + .notify_when_agent_waiting + }, + }), + metadata: None, + files: USER, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Play Sound When Agent Done", + description: "Whether to play a sound when the agent has either completed its response, or needs user input", + field: Box::new(SettingField { + pick: |settings_content| { + if let Some(agent) = &settings_content.agent { + &agent.play_sound_when_agent_done + } else { + &None + } + }, + pick_mut: |settings_content| { + &mut settings_content + .agent + .get_or_insert_default() + .play_sound_when_agent_done + }, + }), + metadata: None, + files: USER, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Expand Edit Card", + description: "Whether to have edit cards in the agent panel expanded, showing a preview of the diff", + field: Box::new(SettingField { + pick: |settings_content| { + if let Some(agent) = &settings_content.agent { + &agent.expand_edit_card + } else { + &None + } + }, + pick_mut: |settings_content| { + &mut settings_content + .agent + .get_or_insert_default() + .expand_edit_card + }, + }), + metadata: None, + files: USER, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Expand Terminal Card", + description: "Whether to have terminal cards in the agent panel expanded, showing the whole command output", + field: Box::new(SettingField { + pick: |settings_content| { + if let Some(agent) = &settings_content.agent { + &agent.expand_terminal_card + } else { + &None + } + }, + pick_mut: |settings_content| { + &mut settings_content + .agent + .get_or_insert_default() + .expand_terminal_card + }, + }), + metadata: None, + files: USER, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Use Modifier To Send", + description: "Whether to always use cmd-enter (or ctrl-enter on Linux or Windows) to send messages", + field: Box::new(SettingField { + pick: |settings_content| { + if let Some(agent) = &settings_content.agent { + &agent.use_modifier_to_send + } else { + &None + } + }, + pick_mut: |settings_content| { + &mut settings_content + .agent + .get_or_insert_default() + .use_modifier_to_send + }, + }), + metadata: None, + files: USER, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Message Editor Min Lines", + description: "Minimum number of lines to display in the agent message editor", + field: Box::new(SettingField { + pick: |settings_content| { + if let Some(agent) = &settings_content.agent { + &agent.message_editor_min_lines + } else { + &None + } + }, + pick_mut: |settings_content| { + &mut settings_content + .agent + .get_or_insert_default() + .message_editor_min_lines + }, + }), + metadata: None, + files: USER, + }), ], }, SettingsPage { diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index 7282bdbf593e6d074d2f39b849fbfa525df37596..431b88f812f4114b0b20d1a2b333366ff792d7b8 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -376,7 +376,8 @@ fn init_renderers(cx: &mut App) { .add_basic_renderer::(render_dropdown) .add_basic_renderer::(render_dropdown) .add_basic_renderer::(render_dropdown) - .add_basic_renderer::(render_dropdown); + .add_basic_renderer::(render_dropdown) + .add_basic_renderer::(render_dropdown); // .add_renderer::(|settings_field, file, _, window, cx| { // render_dropdown(*settings_field, file, window, cx) // });