Detailed changes
@@ -965,6 +965,9 @@
"default_width": 640,
// Default height when the agent panel is docked to the bottom.
"default_height": 320,
+ // Maximum content width when the agent panel is wider than this value.
+ // Content will be centered within the panel.
+ "max_content_width": 850,
// The default model to use when creating new threads.
"default_model": {
// The provider to use.
@@ -574,6 +574,7 @@ mod tests {
flexible: true,
default_width: px(300.),
default_height: px(600.),
+ max_content_width: px(850.),
default_model: None,
inline_assistant_model: None,
inline_assistant_use_streaming_tools: false,
@@ -154,6 +154,7 @@ pub struct AgentSettings {
pub sidebar_side: SidebarDockPosition,
pub default_width: Pixels,
pub default_height: Pixels,
+ pub max_content_width: Pixels,
pub default_model: Option<LanguageModelSelection>,
pub inline_assistant_model: Option<LanguageModelSelection>,
pub inline_assistant_use_streaming_tools: bool,
@@ -600,6 +601,7 @@ impl Settings for AgentSettings {
sidebar_side: agent.sidebar_side.unwrap(),
default_width: px(agent.default_width.unwrap()),
default_height: px(agent.default_height.unwrap()),
+ max_content_width: px(agent.max_content_width.unwrap()),
flexible: agent.flexible.unwrap(),
default_model: Some(agent.default_model.unwrap()),
inline_assistant_model: agent.inline_assistant_model,
@@ -3186,17 +3186,11 @@ impl AgentPanel {
fn render_panel_options_menu(
&self,
- window: &mut Window,
+ _window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
- let full_screen_label = if self.is_zoomed(window, cx) {
- "Disable Full Screen"
- } else {
- "Enable Full Screen"
- };
-
let conversation_view = match &self.active_view {
ActiveView::AgentThread { conversation_view } => Some(conversation_view.clone()),
_ => None,
@@ -3272,8 +3266,7 @@ impl AgentPanel {
.action("Profiles", Box::new(ManageProfiles::default()))
.action("Settings", Box::new(OpenSettings))
.separator()
- .action("Toggle Threads Sidebar", Box::new(ToggleWorkspaceSidebar))
- .action(full_screen_label, Box::new(ToggleZoom));
+ .action("Toggle Threads Sidebar", Box::new(ToggleWorkspaceSidebar));
if has_auth_methods {
menu = menu.action("Reauthenticate", Box::new(ReauthenticateAgent))
@@ -3709,21 +3702,37 @@ impl AgentPanel {
);
let is_full_screen = self.is_zoomed(window, cx);
+ let full_screen_button = if is_full_screen {
+ IconButton::new("disable-full-screen", IconName::Minimize)
+ .icon_size(IconSize::Small)
+ .tooltip(move |_, cx| Tooltip::for_action("Disable Full Screen", &ToggleZoom, cx))
+ .on_click(cx.listener(move |this, _, window, cx| {
+ this.toggle_zoom(&ToggleZoom, window, cx);
+ }))
+ } else {
+ IconButton::new("enable-full-screen", IconName::Maximize)
+ .icon_size(IconSize::Small)
+ .tooltip(move |_, cx| Tooltip::for_action("Enable Full Screen", &ToggleZoom, cx))
+ .on_click(cx.listener(move |this, _, window, cx| {
+ this.toggle_zoom(&ToggleZoom, window, cx);
+ }))
+ };
let use_v2_empty_toolbar = has_v2_flag && is_empty_state && !is_in_history_or_config;
+ let max_content_width = AgentSettings::get_global(cx).max_content_width;
+
let base_container = h_flex()
- .id("agent-panel-toolbar")
- .h(Tab::container_height(cx))
- .max_w_full()
+ .size_full()
+ // TODO: This is only until we remove Agent settings from the panel.
+ .when(!is_in_history_or_config, |this| {
+ this.max_w(max_content_width).mx_auto()
+ })
.flex_none()
.justify_between()
- .gap_2()
- .bg(cx.theme().colors().tab_bar_background)
- .border_b_1()
- .border_color(cx.theme().colors().border);
+ .gap_2();
- if use_v2_empty_toolbar {
+ let toolbar_content = if use_v2_empty_toolbar {
let (chevron_icon, icon_color, label_color) =
if self.new_thread_menu_handle.is_deployed() {
(IconName::ChevronUp, Color::Accent, Color::Accent)
@@ -3805,20 +3814,7 @@ impl AgentPanel {
cx,
))
})
- .when(is_full_screen, |this| {
- this.child(
- IconButton::new("disable-full-screen", IconName::Minimize)
- .icon_size(IconSize::Small)
- .tooltip(move |_, cx| {
- Tooltip::for_action("Disable Full Screen", &ToggleZoom, cx)
- })
- .on_click({
- cx.listener(move |_, _, window, cx| {
- window.dispatch_action(ToggleZoom.boxed_clone(), cx);
- })
- }),
- )
- })
+ .child(full_screen_button)
.child(self.render_panel_options_menu(window, cx)),
)
.into_any_element()
@@ -3871,24 +3867,21 @@ impl AgentPanel {
cx,
))
})
- .when(is_full_screen, |this| {
- this.child(
- IconButton::new("disable-full-screen", IconName::Minimize)
- .icon_size(IconSize::Small)
- .tooltip(move |_, cx| {
- Tooltip::for_action("Disable Full Screen", &ToggleZoom, cx)
- })
- .on_click({
- cx.listener(move |_, _, window, cx| {
- window.dispatch_action(ToggleZoom.boxed_clone(), cx);
- })
- }),
- )
- })
+ .child(full_screen_button)
.child(self.render_panel_options_menu(window, cx)),
)
.into_any_element()
- }
+ };
+
+ h_flex()
+ .id("agent-panel-toolbar")
+ .h(Tab::container_height(cx))
+ .flex_shrink_0()
+ .max_w_full()
+ .bg(cx.theme().colors().tab_bar_background)
+ .border_b_1()
+ .border_color(cx.theme().colors().border)
+ .child(toolbar_content)
}
fn render_worktree_creation_status(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
@@ -742,6 +742,7 @@ mod tests {
flexible: true,
default_width: px(300.),
default_height: px(600.),
+ max_content_width: px(850.),
default_model: None,
inline_assistant_model: None,
inline_assistant_use_streaming_tools: false,
@@ -3014,14 +3014,12 @@ impl ThreadView {
let is_done = thread.read(cx).status() == ThreadStatus::Idle;
let is_canceled_or_failed = self.is_subagent_canceled_or_failed(cx);
+ let max_content_width = AgentSettings::get_global(cx).max_content_width;
+
Some(
h_flex()
- .h(Tab::container_height(cx))
- .pl_2()
- .pr_1p5()
.w_full()
- .justify_between()
- .gap_1()
+ .h(Tab::container_height(cx))
.border_b_1()
.when(is_done && is_canceled_or_failed, |this| {
this.border_dashed()
@@ -3030,50 +3028,61 @@ impl ThreadView {
.bg(cx.theme().colors().editor_background.opacity(0.2))
.child(
h_flex()
- .flex_1()
- .gap_2()
+ .size_full()
+ .max_w(max_content_width)
+ .mx_auto()
+ .pl_2()
+ .pr_1()
+ .flex_shrink_0()
+ .justify_between()
+ .gap_1()
.child(
- Icon::new(IconName::ForwardArrowUp)
- .size(IconSize::Small)
- .color(Color::Muted),
+ h_flex()
+ .flex_1()
+ .gap_2()
+ .child(
+ Icon::new(IconName::ForwardArrowUp)
+ .size(IconSize::Small)
+ .color(Color::Muted),
+ )
+ .child(self.title_editor.clone())
+ .when(is_done && is_canceled_or_failed, |this| {
+ this.child(Icon::new(IconName::Close).color(Color::Error))
+ })
+ .when(is_done && !is_canceled_or_failed, |this| {
+ this.child(Icon::new(IconName::Check).color(Color::Success))
+ }),
)
- .child(self.title_editor.clone())
- .when(is_done && is_canceled_or_failed, |this| {
- this.child(Icon::new(IconName::Close).color(Color::Error))
- })
- .when(is_done && !is_canceled_or_failed, |this| {
- this.child(Icon::new(IconName::Check).color(Color::Success))
- }),
- )
- .child(
- h_flex()
- .gap_0p5()
- .when(!is_done, |this| {
- this.child(
- IconButton::new("stop_subagent", IconName::Stop)
- .icon_size(IconSize::Small)
- .icon_color(Color::Error)
- .tooltip(Tooltip::text("Stop Subagent"))
- .on_click(move |_, _, cx| {
- thread.update(cx, |thread, cx| {
- thread.cancel(cx).detach();
- });
- }),
- )
- })
.child(
- IconButton::new("minimize_subagent", IconName::Minimize)
- .icon_size(IconSize::Small)
- .tooltip(Tooltip::text("Minimize Subagent"))
- .on_click(move |_, window, cx| {
- let _ = server_view.update(cx, |server_view, cx| {
- server_view.navigate_to_session(
- parent_session_id.clone(),
- window,
- cx,
- );
- });
- }),
+ h_flex()
+ .gap_0p5()
+ .when(!is_done, |this| {
+ this.child(
+ IconButton::new("stop_subagent", IconName::Stop)
+ .icon_size(IconSize::Small)
+ .icon_color(Color::Error)
+ .tooltip(Tooltip::text("Stop Subagent"))
+ .on_click(move |_, _, cx| {
+ thread.update(cx, |thread, cx| {
+ thread.cancel(cx).detach();
+ });
+ }),
+ )
+ })
+ .child(
+ IconButton::new("minimize_subagent", IconName::Dash)
+ .icon_size(IconSize::Small)
+ .tooltip(Tooltip::text("Minimize Subagent"))
+ .on_click(move |_, window, cx| {
+ let _ = server_view.update(cx, |server_view, cx| {
+ server_view.navigate_to_session(
+ parent_session_id.clone(),
+ window,
+ cx,
+ );
+ });
+ }),
+ ),
),
),
)
@@ -3099,6 +3108,8 @@ impl ThreadView {
(IconName::Maximize, "Expand Message Editor")
};
+ let max_content_width = AgentSettings::get_global(cx).max_content_width;
+
v_flex()
.on_action(cx.listener(Self::expand_message_editor))
.p_2()
@@ -3113,73 +3124,80 @@ impl ThreadView {
})
.child(
v_flex()
- .relative()
- .size_full()
- .when(v2_empty_state, |this| this.flex_1())
- .pt_1()
- .pr_2p5()
- .child(self.message_editor.clone())
- .when(!v2_empty_state, |this| {
- this.child(
- h_flex()
- .absolute()
- .top_0()
- .right_0()
- .opacity(0.5)
- .hover(|this| this.opacity(1.0))
- .child(
- IconButton::new("toggle-height", expand_icon)
- .icon_size(IconSize::Small)
- .icon_color(Color::Muted)
- .tooltip({
- move |_window, cx| {
- Tooltip::for_action_in(
- expand_tooltip,
- &ExpandMessageEditor,
- &focus_handle,
- cx,
- )
- }
- })
- .on_click(cx.listener(|this, _, window, cx| {
- this.expand_message_editor(
- &ExpandMessageEditor,
- window,
- cx,
- );
- })),
- ),
- )
- }),
- )
- .child(
- h_flex()
- .flex_none()
- .flex_wrap()
- .justify_between()
+ .flex_1()
+ .w_full()
+ .max_w(max_content_width)
+ .mx_auto()
.child(
- h_flex()
- .gap_0p5()
- .child(self.render_add_context_button(cx))
- .child(self.render_follow_toggle(cx))
- .children(self.render_fast_mode_control(cx))
- .children(self.render_thinking_control(cx)),
+ v_flex()
+ .relative()
+ .size_full()
+ .when(v2_empty_state, |this| this.flex_1())
+ .pt_1()
+ .pr_2p5()
+ .child(self.message_editor.clone())
+ .when(!v2_empty_state, |this| {
+ this.child(
+ h_flex()
+ .absolute()
+ .top_0()
+ .right_0()
+ .opacity(0.5)
+ .hover(|this| this.opacity(1.0))
+ .child(
+ IconButton::new("toggle-height", expand_icon)
+ .icon_size(IconSize::Small)
+ .icon_color(Color::Muted)
+ .tooltip({
+ move |_window, cx| {
+ Tooltip::for_action_in(
+ expand_tooltip,
+ &ExpandMessageEditor,
+ &focus_handle,
+ cx,
+ )
+ }
+ })
+ .on_click(cx.listener(|this, _, window, cx| {
+ this.expand_message_editor(
+ &ExpandMessageEditor,
+ window,
+ cx,
+ );
+ })),
+ ),
+ )
+ }),
)
.child(
h_flex()
- .gap_1()
- .children(self.render_token_usage(cx))
- .children(self.profile_selector.clone())
- .map(|this| {
- // Either config_options_view OR (mode_selector + model_selector)
- match self.config_options_view.clone() {
- Some(config_view) => this.child(config_view),
- None => this
- .children(self.mode_selector.clone())
- .children(self.model_selector.clone()),
- }
- })
- .child(self.render_send_button(cx)),
+ .flex_none()
+ .flex_wrap()
+ .justify_between()
+ .child(
+ h_flex()
+ .gap_0p5()
+ .child(self.render_add_context_button(cx))
+ .child(self.render_follow_toggle(cx))
+ .children(self.render_fast_mode_control(cx))
+ .children(self.render_thinking_control(cx)),
+ )
+ .child(
+ h_flex()
+ .gap_1()
+ .children(self.render_token_usage(cx))
+ .children(self.profile_selector.clone())
+ .map(|this| {
+ // Either config_options_view OR (mode_selector + model_selector)
+ match self.config_options_view.clone() {
+ Some(config_view) => this.child(config_view),
+ None => this
+ .children(self.mode_selector.clone())
+ .children(self.model_selector.clone()),
+ }
+ })
+ .child(self.render_send_button(cx)),
+ ),
),
)
.into_any()
@@ -8559,8 +8577,12 @@ impl Render for ThreadView {
let has_messages = self.list_state.item_count() > 0;
let v2_empty_state = cx.has_flag::<AgentV2FeatureFlag>() && !has_messages;
+ let max_content_width = AgentSettings::get_global(cx).max_content_width;
+
let conversation = v_flex()
- .when(!v2_empty_state, |this| this.flex_1())
+ .mx_auto()
+ .max_w(max_content_width)
+ .when(!v2_empty_state, |this| this.flex_1().size_full())
.map(|this| {
let this = this.when(self.resumed_without_history, |this| {
this.child(Self::render_resume_notice(cx))
@@ -128,6 +128,12 @@ pub struct AgentSettingsContent {
/// Default: 320
#[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
pub default_height: Option<f32>,
+ /// Maximum content width in pixels for the agent panel. Content will be
+ /// centered when the panel is wider than this value.
+ ///
+ /// Default: 850
+ #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
+ pub max_content_width: Option<f32>,
/// The default model to use when creating new chats and for other features when a specific model is not specified.
pub default_model: Option<LanguageModelSelection>,
/// Favorite models to show at the top of the model selector.
@@ -5737,7 +5737,7 @@ fn panels_page() -> SettingsPage {
]
}
- fn agent_panel_section() -> [SettingsPageItem; 6] {
+ fn agent_panel_section() -> [SettingsPageItem; 7] {
[
SettingsPageItem::SectionHeader("Agent Panel"),
SettingsPageItem::SettingItem(SettingItem {
@@ -5812,6 +5812,24 @@ fn panels_page() -> SettingsPage {
metadata: None,
files: USER,
}),
+ SettingsPageItem::SettingItem(SettingItem {
+ title: "Agent Panel Max Content Width",
+ description: "Maximum content width in pixels. Content will be centered when the panel is wider than this value.",
+ field: Box::new(SettingField {
+ json_path: Some("agent.max_content_width"),
+ pick: |settings_content| {
+ settings_content.agent.as_ref()?.max_content_width.as_ref()
+ },
+ write: |settings_content, value| {
+ settings_content
+ .agent
+ .get_or_insert_default()
+ .max_content_width = value;
+ },
+ }),
+ metadata: None,
+ files: USER,
+ }),
]
}