agent: Add design tweaks (#28963)

One more batch of fine-tuning the agent panel's design.

Release Notes:

- N/A
This commit is contained in:
Danilo Leal 2025-04-17 12:20:25 -03:00 committed by GitHub
parent 8117940aca
commit 2a878ee6d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 161 additions and 87 deletions

View file

@ -501,11 +501,13 @@ fn render_markdown_code_block(
let codeblock_header = h_flex() let codeblock_header = h_flex()
.group("codeblock_header") .group("codeblock_header")
.p_1() .py_1()
.pl_1p5()
.pr_1()
.gap_1() .gap_1()
.justify_between() .justify_between()
.border_b_1() .border_b_1()
.border_color(cx.theme().colors().border_variant) .border_color(cx.theme().colors().border.opacity(0.6))
.bg(codeblock_header_bg) .bg(codeblock_header_bg)
.rounded_t_md() .rounded_t_md()
.children(label) .children(label)
@ -599,7 +601,7 @@ fn render_markdown_code_block(
.overflow_hidden() .overflow_hidden()
.rounded_lg() .rounded_lg()
.border_1() .border_1()
.border_color(cx.theme().colors().border_variant) .border_color(cx.theme().colors().border.opacity(0.6))
.bg(cx.theme().colors().editor_background) .bg(cx.theme().colors().editor_background)
.child(codeblock_header) .child(codeblock_header)
.when( .when(
@ -1504,7 +1506,14 @@ impl ActiveThread {
window.dispatch_action(Box::new(OpenActiveThreadAsMarkdown), cx) window.dispatch_action(Box::new(OpenActiveThreadAsMarkdown), cx)
}); });
let feedback_container = h_flex().py_2().px_4().gap_1().justify_between(); // For all items that should be aligned with the Assistant's response.
const RESPONSE_PADDING_X: Pixels = px(18.);
let feedback_container = h_flex()
.py_2()
.px(RESPONSE_PADDING_X)
.gap_1()
.justify_between();
let feedback_items = match self.thread.read(cx).message_feedback(message_id) { let feedback_items = match self.thread.read(cx).message_feedback(message_id) {
Some(feedback) => feedback_container Some(feedback) => feedback_container
.child( .child(
@ -1705,9 +1714,9 @@ impl ActiveThread {
this.pt_4() this.pt_4()
} }
}) })
.pb_4()
.pl_2() .pl_2()
.pr_2p5() .pr_2p5()
.pb_4()
.child( .child(
v_flex() v_flex()
.bg(colors.editor_background) .bg(colors.editor_background)
@ -1814,9 +1823,8 @@ impl ActiveThread {
), ),
Role::Assistant => v_flex() Role::Assistant => v_flex()
.id(("message-container", ix)) .id(("message-container", ix))
.ml_2p5() .px(RESPONSE_PADDING_X)
.pl_2() .gap_2()
.pr_4()
.children(message_content) .children(message_content)
.when(has_tool_uses, |parent| { .when(has_tool_uses, |parent| {
parent.children( parent.children(
@ -1840,9 +1848,10 @@ impl ActiveThread {
message_id > *editing_message_id message_id > *editing_message_id
}); });
let panel_background = cx.theme().colors().panel_background;
v_flex() v_flex()
.w_full() .w_full()
.when(after_editing_message, |parent| parent.opacity(0.2))
.when_some(checkpoint, |parent, checkpoint| { .when_some(checkpoint, |parent, checkpoint| {
let mut is_pending = false; let mut is_pending = false;
let mut error = None; let mut error = None;
@ -2004,6 +2013,18 @@ impl ActiveThread {
}, },
) )
}) })
.when(after_editing_message, |parent| {
// Backdrop to dim out the whole thread below the editing user message
parent.relative().child(
div()
.occlude()
.absolute()
.inset_0()
.size_full()
.bg(panel_background)
.opacity(0.8),
)
})
.into_any() .into_any()
} }
@ -2030,6 +2051,15 @@ impl ActiveThread {
None None
}; };
let message_role = self
.thread
.read(cx)
.message(message_id)
.map(|m| m.role)
.unwrap_or(Role::User);
let is_assistant = message_role == Role::Assistant;
v_flex() v_flex()
.text_ui(cx) .text_ui(cx)
.gap_2() .gap_2()
@ -2050,80 +2080,100 @@ impl ActiveThread {
cx, cx,
) )
.into_any_element(), .into_any_element(),
RenderedMessageSegment::Text(markdown) => div() RenderedMessageSegment::Text(markdown) => {
.child( let markdown_element = MarkdownElement::new(
MarkdownElement::new( markdown.clone(),
markdown.clone(), default_markdown_style(window, cx),
default_markdown_style(window, cx), );
)
.code_block_renderer(markdown::CodeBlockRenderer::Custom {
render: Arc::new({
let workspace = workspace.clone();
let active_thread = cx.entity();
move |kind, parsed_markdown, range, metadata, window, cx| {
render_markdown_code_block(
message_id,
range.start,
kind,
parsed_markdown,
metadata,
active_thread.clone(),
workspace.clone(),
window,
cx,
)
}
}),
transform: Some(Arc::new({
let active_thread = cx.entity();
move |el, range, metadata, _, cx| {
let is_expanded = active_thread
.read(cx)
.expanded_code_blocks
.get(&(message_id, range.start))
.copied()
.unwrap_or(false);
if is_expanded let markdown_element = if is_assistant {
|| metadata.line_count markdown_element.code_block_renderer(
<= MAX_UNCOLLAPSED_LINES_IN_CODE_BLOCK markdown::CodeBlockRenderer::Custom {
{ render: Arc::new({
return el; let workspace = workspace.clone();
let active_thread = cx.entity();
move |kind,
parsed_markdown,
range,
metadata,
window,
cx| {
render_markdown_code_block(
message_id,
range.start,
kind,
parsed_markdown,
metadata,
active_thread.clone(),
workspace.clone(),
window,
cx,
)
} }
el.child( }),
div() transform: Some(Arc::new({
.absolute() let active_thread = cx.entity();
.bottom_0() move |el, range, metadata, _, cx| {
.left_0() let is_expanded = active_thread
.w_full() .read(cx)
.h_1_4() .expanded_code_blocks
.rounded_b_lg() .get(&(message_id, range.start))
.bg(gpui::linear_gradient( .copied()
0., .unwrap_or(false);
gpui::linear_color_stop(
cx.theme().colors().editor_background, if is_expanded
|| metadata.line_count
<= MAX_UNCOLLAPSED_LINES_IN_CODE_BLOCK
{
return el;
}
el.child(
div()
.absolute()
.bottom_0()
.left_0()
.w_full()
.h_1_4()
.rounded_b_lg()
.bg(gpui::linear_gradient(
0., 0.,
), gpui::linear_color_stop(
gpui::linear_color_stop( cx.theme()
cx.theme() .colors()
.colors() .editor_background,
.editor_background 0.,
.opacity(0.), ),
1., gpui::linear_color_stop(
), cx.theme()
)), .colors()
) .editor_background
} .opacity(0.),
})), 1.,
}) ),
.on_url_click({ )),
)
}
})),
},
)
} else {
markdown_element.code_block_renderer(
markdown::CodeBlockRenderer::Default {
copy_button: false,
border: true,
},
)
};
div()
.child(markdown_element.on_url_click({
let workspace = self.workspace.clone(); let workspace = self.workspace.clone();
move |text, window, cx| { move |text, window, cx| {
open_markdown_link(text, workspace.clone(), window, cx); open_markdown_link(text, workspace.clone(), window, cx);
} }
}), }))
) .into_any_element()
.into_any_element(), }
}, },
), ),
) )
@ -2392,6 +2442,7 @@ impl ActiveThread {
.get(&tool_use.id) .get(&tool_use.id)
.copied() .copied()
.unwrap_or_default(); .unwrap_or_default();
let is_status_finished = matches!(&tool_use.status, ToolUseStatus::Finished(_)); let is_status_finished = matches!(&tool_use.status, ToolUseStatus::Finished(_));
let fs = self let fs = self
@ -2452,6 +2503,7 @@ impl ActiveThread {
) )
.code_block_renderer(markdown::CodeBlockRenderer::Default { .code_block_renderer(markdown::CodeBlockRenderer::Default {
copy_button: false, copy_button: false,
border: false,
}) })
.on_url_click({ .on_url_click({
let workspace = self.workspace.clone(); let workspace = self.workspace.clone();
@ -2481,6 +2533,7 @@ impl ActiveThread {
) )
.code_block_renderer(markdown::CodeBlockRenderer::Default { .code_block_renderer(markdown::CodeBlockRenderer::Default {
copy_button: false, copy_button: false,
border: false,
}) })
.on_url_click({ .on_url_click({
let workspace = self.workspace.clone(); let workspace = self.workspace.clone();
@ -2583,11 +2636,10 @@ impl ActiveThread {
)) ))
}; };
div().map(|element| { v_flex().gap_1().mb_3().map(|element| {
if !edit_tools { if !edit_tools {
element.child( element.child(
v_flex() v_flex()
.my_2()
.child( .child(
h_flex() h_flex()
.group("disclosure-header") .group("disclosure-header")
@ -2609,7 +2661,7 @@ impl ActiveThread {
.color(Color::Muted), .color(Color::Muted),
) )
.child( .child(
h_flex().pr_8().text_ui_sm(cx).children( h_flex().pr_8().text_size(rems(0.8125)).children(
rendered_tool_use.map(|rendered| MarkdownElement::new(rendered.label, tool_use_markdown_style(window, cx)).on_url_click({let workspace = self.workspace.clone(); move |text, window, cx| { rendered_tool_use.map(|rendered| MarkdownElement::new(rendered.label, tool_use_markdown_style(window, cx)).on_url_click({let workspace = self.workspace.clone(); move |text, window, cx| {
open_markdown_link(text, workspace.clone(), window, cx); open_markdown_link(text, workspace.clone(), window, cx);
}})) }}))
@ -2659,7 +2711,7 @@ impl ActiveThread {
) )
} else { } else {
v_flex() v_flex()
.my_2() .mb_2()
.rounded_lg() .rounded_lg()
.border_1() .border_1()
.border_color(self.tool_card_border_color(cx)) .border_color(self.tool_card_border_color(cx))

View file

@ -1180,8 +1180,8 @@ impl AssistantPanel {
parent parent
.child( .child(
h_flex() h_flex()
.mr_0p5() .mr_1()
.size_2() .size_2p5()
.justify_center() .justify_center()
.rounded_full() .rounded_full()
.bg(cx.theme().colors().text.opacity(0.1)) .bg(cx.theme().colors().text.opacity(0.1))

View file

@ -632,6 +632,7 @@ impl CompletionsMenu {
MarkdownElement::new(markdown.clone(), hover_markdown_style(window, cx)) MarkdownElement::new(markdown.clone(), hover_markdown_style(window, cx))
.code_block_renderer(markdown::CodeBlockRenderer::Default { .code_block_renderer(markdown::CodeBlockRenderer::Default {
copy_button: false, copy_button: false,
border: false,
}) })
.on_url_click(open_markdown_url), .on_url_click(open_markdown_url),
) )

View file

@ -841,6 +841,7 @@ impl InfoPopover {
MarkdownElement::new(markdown, hover_markdown_style(window, cx)) MarkdownElement::new(markdown, hover_markdown_style(window, cx))
.code_block_renderer(markdown::CodeBlockRenderer::Default { .code_block_renderer(markdown::CodeBlockRenderer::Default {
copy_button: false, copy_button: false,
border: false,
}) })
.on_url_click(open_markdown_url), .on_url_click(open_markdown_url),
), ),
@ -969,6 +970,7 @@ impl DiagnosticPopover {
}) })
.code_block_renderer(markdown::CodeBlockRenderer::Default { .code_block_renderer(markdown::CodeBlockRenderer::Default {
copy_button: false, copy_button: false,
border: false,
}) })
.on_url_click(open_markdown_url), .on_url_click(open_markdown_url),
) )

View file

@ -107,6 +107,7 @@ struct Options {
pub enum CodeBlockRenderer { pub enum CodeBlockRenderer {
Default { Default {
copy_button: bool, copy_button: bool,
border: bool,
}, },
Custom { Custom {
render: CodeBlockRenderFn, render: CodeBlockRenderFn,
@ -381,7 +382,10 @@ impl MarkdownElement {
Self { Self {
markdown, markdown,
style, style,
code_block_renderer: CodeBlockRenderer::Default { copy_button: true }, code_block_renderer: CodeBlockRenderer::Default {
copy_button: true,
border: false,
},
on_url_click: None, on_url_click: None,
} }
} }
@ -748,6 +752,21 @@ impl Element for MarkdownElement {
code_block.w_full() code_block.w_full()
} }
}); });
// Apply border if required
// Usage examples:
// CodeBlockRenderer::Default { copy_button: true, border: true } - Both copy button and border
// CodeBlockRenderer::Default { copy_button: false, border: true } - Border only
// CodeBlockRenderer::Default { copy_button: true, border: false } - Copy button only (default)
if let CodeBlockRenderer::Default { border: true, .. } =
&self.code_block_renderer
{
code_block = code_block
.rounded_md()
.border_1()
.border_color(cx.theme().colors().border_variant);
}
code_block.style().refine(&self.style.code_block); code_block.style().refine(&self.style.code_block);
if let Some(code_block_text_style) = &self.style.code_block.text if let Some(code_block_text_style) = &self.style.code_block.text
{ {
@ -947,10 +966,10 @@ impl Element for MarkdownElement {
}); });
} }
if matches!( if let CodeBlockRenderer::Default {
&self.code_block_renderer, copy_button: true, ..
CodeBlockRenderer::Default { copy_button: true } } = &self.code_block_renderer
) { {
builder.flush_text(); builder.flush_text();
builder.modify_current_div(|el| { builder.modify_current_div(|el| {
let content_range = parser::extract_code_block_content_range( let content_range = parser::extract_code_block_content_range(