From 2a878ee6d07258381e64d44abdd447e34532f625 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Thu, 17 Apr 2025 12:20:25 -0300 Subject: [PATCH] agent: Add design tweaks (#28963) One more batch of fine-tuning the agent panel's design. Release Notes: - N/A --- crates/agent/src/active_thread.rs | 212 +++++++++++++++--------- crates/agent/src/assistant_panel.rs | 4 +- crates/editor/src/code_context_menus.rs | 1 + crates/editor/src/hover_popover.rs | 2 + crates/markdown/src/markdown.rs | 29 +++- 5 files changed, 161 insertions(+), 87 deletions(-) diff --git a/crates/agent/src/active_thread.rs b/crates/agent/src/active_thread.rs index bab8d72f57..79fb331f30 100644 --- a/crates/agent/src/active_thread.rs +++ b/crates/agent/src/active_thread.rs @@ -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)) diff --git a/crates/agent/src/assistant_panel.rs b/crates/agent/src/assistant_panel.rs index f6b0915072..002f3ebcbe 100644 --- a/crates/agent/src/assistant_panel.rs +++ b/crates/agent/src/assistant_panel.rs @@ -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)) diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index de8416a57f..86af09309c 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -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), ) diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 55a35fb11f..3ab6c95d5e 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -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), ) diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index 03039f41fe..68b3788966 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -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(