settings_ui: Add some AI settings (#40111)

Danilo Leal created

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

Change summary

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(-)

Detailed changes

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<Self>) -> 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<Self>) -> 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<Self>) -> 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<Self>) -> 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<Self>) -> 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<Plan>, cx: &mut Context<Self>) -> 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)),

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]

crates/settings_ui/src/page_data.rs 🔗

@@ -3122,6 +3122,82 @@ pub(crate) fn settings_data() -> Vec<SettingsPage> {
                     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<SettingsPage> {
                     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 {

crates/settings_ui/src/settings_ui.rs 🔗

@@ -376,7 +376,8 @@ fn init_renderers(cx: &mut App) {
         .add_basic_renderer::<settings::DisplayIn>(render_dropdown)
         .add_basic_renderer::<settings::MinimapThumb>(render_dropdown)
         .add_basic_renderer::<settings::MinimapThumbBorder>(render_dropdown)
-        .add_basic_renderer::<settings::SteppingGranularity>(render_dropdown);
+        .add_basic_renderer::<settings::SteppingGranularity>(render_dropdown)
+        .add_basic_renderer::<settings::NotifyWhenAgentWaiting>(render_dropdown);
     // .add_renderer::<ThemeSelection>(|settings_field, file, _, window, cx| {
     //     render_dropdown(*settings_field, file, window, cx)
     // });