mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
agent_ui: Set max-width for thread view content (#52730)
This PR adds a configurable max-width to the agent panel. This will be particularly useful when opting into an agentic-first layout where the thread will be at the center of the UI (with the panel most likely full-screen'ed, which is why I'm also adding here the button to make it full screen in the toolbar). The default max-width is 850, which is a bit bigger than the one generally considered as a standard (~66 characters wide, which usually sums up to 750 pixels). Release Notes: - Agent: Added a max-width to the thread view for better readability, particularly when the panel is zoomed in.
This commit is contained in:
parent
eaf14d028a
commit
0bde5094f6
8 changed files with 204 additions and 158 deletions
|
|
@ -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),
|
||||
)
|
||||
.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()
|
||||
.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(
|
||||
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,
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue