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()
.group("codeblock_header")
.p_1()
.py_1()
.pl_1p5()
.pr_1()
.gap_1()
.justify_between()
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.border_color(cx.theme().colors().border.opacity(0.6))
.bg(codeblock_header_bg)
.rounded_t_md()
.children(label)
@ -599,7 +601,7 @@ fn render_markdown_code_block(
.overflow_hidden()
.rounded_lg()
.border_1()
.border_color(cx.theme().colors().border_variant)
.border_color(cx.theme().colors().border.opacity(0.6))
.bg(cx.theme().colors().editor_background)
.child(codeblock_header)
.when(
@ -1504,7 +1506,14 @@ impl ActiveThread {
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) {
Some(feedback) => feedback_container
.child(
@ -1705,9 +1714,9 @@ impl ActiveThread {
this.pt_4()
}
})
.pb_4()
.pl_2()
.pr_2p5()
.pb_4()
.child(
v_flex()
.bg(colors.editor_background)
@ -1814,9 +1823,8 @@ impl ActiveThread {
),
Role::Assistant => v_flex()
.id(("message-container", ix))
.ml_2p5()
.pl_2()
.pr_4()
.px(RESPONSE_PADDING_X)
.gap_2()
.children(message_content)
.when(has_tool_uses, |parent| {
parent.children(
@ -1840,9 +1848,10 @@ impl ActiveThread {
message_id > *editing_message_id
});
let panel_background = cx.theme().colors().panel_background;
v_flex()
.w_full()
.when(after_editing_message, |parent| parent.opacity(0.2))
.when_some(checkpoint, |parent, checkpoint| {
let mut is_pending = false;
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()
}
@ -2030,6 +2051,15 @@ impl ActiveThread {
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()
.text_ui(cx)
.gap_2()
@ -2050,80 +2080,100 @@ impl ActiveThread {
cx,
)
.into_any_element(),
RenderedMessageSegment::Text(markdown) => div()
.child(
MarkdownElement::new(
markdown.clone(),
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);
RenderedMessageSegment::Text(markdown) => {
let markdown_element = MarkdownElement::new(
markdown.clone(),
default_markdown_style(window, cx),
);
if is_expanded
|| metadata.line_count
<= MAX_UNCOLLAPSED_LINES_IN_CODE_BLOCK
{
return el;
let markdown_element = if is_assistant {
markdown_element.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,
)
}
el.child(
div()
.absolute()
.bottom_0()
.left_0()
.w_full()
.h_1_4()
.rounded_b_lg()
.bg(gpui::linear_gradient(
0.,
gpui::linear_color_stop(
cx.theme().colors().editor_background,
}),
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
|| 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.,
),
gpui::linear_color_stop(
cx.theme()
.colors()
.editor_background
.opacity(0.),
1.,
),
)),
)
}
})),
})
.on_url_click({
gpui::linear_color_stop(
cx.theme()
.colors()
.editor_background,
0.,
),
gpui::linear_color_stop(
cx.theme()
.colors()
.editor_background
.opacity(0.),
1.,
),
)),
)
}
})),
},
)
} 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();
move |text, 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)
.copied()
.unwrap_or_default();
let is_status_finished = matches!(&tool_use.status, ToolUseStatus::Finished(_));
let fs = self
@ -2452,6 +2503,7 @@ impl ActiveThread {
)
.code_block_renderer(markdown::CodeBlockRenderer::Default {
copy_button: false,
border: false,
})
.on_url_click({
let workspace = self.workspace.clone();
@ -2481,6 +2533,7 @@ impl ActiveThread {
)
.code_block_renderer(markdown::CodeBlockRenderer::Default {
copy_button: false,
border: false,
})
.on_url_click({
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 {
element.child(
v_flex()
.my_2()
.child(
h_flex()
.group("disclosure-header")
@ -2609,7 +2661,7 @@ impl ActiveThread {
.color(Color::Muted),
)
.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| {
open_markdown_link(text, workspace.clone(), window, cx);
}}))
@ -2659,7 +2711,7 @@ impl ActiveThread {
)
} else {
v_flex()
.my_2()
.mb_2()
.rounded_lg()
.border_1()
.border_color(self.tool_card_border_color(cx))

View file

@ -1180,8 +1180,8 @@ impl AssistantPanel {
parent
.child(
h_flex()
.mr_0p5()
.size_2()
.mr_1()
.size_2p5()
.justify_center()
.rounded_full()
.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))
.code_block_renderer(markdown::CodeBlockRenderer::Default {
copy_button: false,
border: false,
})
.on_url_click(open_markdown_url),
)

View file

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

View file

@ -107,6 +107,7 @@ struct Options {
pub enum CodeBlockRenderer {
Default {
copy_button: bool,
border: bool,
},
Custom {
render: CodeBlockRenderFn,
@ -381,7 +382,10 @@ impl MarkdownElement {
Self {
markdown,
style,
code_block_renderer: CodeBlockRenderer::Default { copy_button: true },
code_block_renderer: CodeBlockRenderer::Default {
copy_button: true,
border: false,
},
on_url_click: None,
}
}
@ -748,6 +752,21 @@ impl Element for MarkdownElement {
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);
if let Some(code_block_text_style) = &self.style.code_block.text
{
@ -947,10 +966,10 @@ impl Element for MarkdownElement {
});
}
if matches!(
&self.code_block_renderer,
CodeBlockRenderer::Default { copy_button: true }
) {
if let CodeBlockRenderer::Default {
copy_button: true, ..
} = &self.code_block_renderer
{
builder.flush_text();
builder.modify_current_div(|el| {
let content_range = parser::extract_code_block_content_range(