diff --git a/crates/agent2/src/tools/terminal_tool.rs b/crates/agent2/src/tools/terminal_tool.rs index 7acfc2455093eac0f3d15e840abce47f38a6c8b0..6d30c19152001deaef5deeacbdf266e28ac03d08 100644 --- a/crates/agent2/src/tools/terminal_tool.rs +++ b/crates/agent2/src/tools/terminal_tool.rs @@ -82,7 +82,7 @@ impl AgentTool for TerminalTool { .into(), } } else { - "Run terminal command".into() + "".into() } } diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index cf6f563ecdeb5a60e2b3faa4a5ee36d3282e1bdb..4a91a9fff7b9902bc51301752a578114159bf680 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -2079,27 +2079,6 @@ impl AcpThreadView { let has_location = tool_call.locations.len() == 1; let card_header_id = SharedString::from("inner-tool-call-header"); - let tool_icon = if tool_call.kind == acp::ToolKind::Edit && has_location { - FileIcons::get_icon(&tool_call.locations[0].path, cx) - .map(Icon::from_path) - .unwrap_or(Icon::new(IconName::ToolPencil)) - } else { - Icon::new(match tool_call.kind { - acp::ToolKind::Read => IconName::ToolSearch, - acp::ToolKind::Edit => IconName::ToolPencil, - acp::ToolKind::Delete => IconName::ToolDeleteFile, - acp::ToolKind::Move => IconName::ArrowRightLeft, - acp::ToolKind::Search => IconName::ToolSearch, - acp::ToolKind::Execute => IconName::ToolTerminal, - acp::ToolKind::Think => IconName::ToolThink, - acp::ToolKind::Fetch => IconName::ToolWeb, - acp::ToolKind::SwitchMode => IconName::ArrowRightLeft, - acp::ToolKind::Other => IconName::ToolHammer, - }) - } - .size(IconSize::Small) - .color(Color::Muted); - let failed_or_canceled = match &tool_call.status { ToolCallStatus::Rejected | ToolCallStatus::Canceled | ToolCallStatus::Failed => true, _ => false, @@ -2109,41 +2088,16 @@ impl AcpThreadView { tool_call.status, ToolCallStatus::WaitingForConfirmation { .. } ); + let is_terminal_tool = matches!(tool_call.kind, acp::ToolKind::Execute); let is_edit = matches!(tool_call.kind, acp::ToolKind::Edit) || tool_call.diffs().next().is_some(); - let use_card_layout = needs_confirmation || is_edit; + + let use_card_layout = needs_confirmation || is_edit || is_terminal_tool; let is_collapsible = !tool_call.content.is_empty() && !needs_confirmation; let is_open = needs_confirmation || self.expanded_tool_calls.contains(&tool_call.id); - let gradient_overlay = { - div() - .absolute() - .top_0() - .right_0() - .w_12() - .h_full() - .map(|this| { - if use_card_layout { - this.bg(linear_gradient( - 90., - linear_color_stop(self.tool_card_header_bg(cx), 1.), - linear_color_stop(self.tool_card_header_bg(cx).opacity(0.2), 0.), - )) - } else { - this.bg(linear_gradient( - 90., - linear_color_stop(cx.theme().colors().panel_background, 1.), - linear_color_stop( - cx.theme().colors().panel_background.opacity(0.2), - 0., - ), - )) - } - }) - }; - let tool_output_display = if is_open { match &tool_call.status { @@ -2228,104 +2182,202 @@ impl AcpThreadView { } }) .mr_5() - .child( - h_flex() - .group(&card_header_id) - .relative() - .w_full() - .gap_1() - .justify_between() - .when(use_card_layout, |this| { - this.p_0p5() - .rounded_t(rems_from_px(5.)) + .map(|this| { + if is_terminal_tool { + this.child( + v_flex() + .p_1p5() + .gap_0p5() + .text_ui_sm(cx) .bg(self.tool_card_header_bg(cx)) - }) - .child( + .child( + Label::new("Run Command") + .buffer_font(cx) + .size(LabelSize::XSmall) + .color(Color::Muted), + ) + .child( + MarkdownElement::new( + tool_call.label.clone(), + terminal_command_markdown_style(window, cx), + ) + .code_block_renderer( + markdown::CodeBlockRenderer::Default { + copy_button: false, + copy_button_on_hover: false, + border: false, + }, + ) + ), + ) + } else { + this.child( h_flex() + .group(&card_header_id) .relative() .w_full() - .h(window.line_height() - px(2.)) - .text_size(self.tool_name_font_size()) - .gap_1p5() - .when(has_location || use_card_layout, |this| this.px_1()) - .when(has_location, |this| { - this.cursor(CursorStyle::PointingHand) - .rounded(rems_from_px(3.)) // Concentric border radius - .hover(|s| s.bg(cx.theme().colors().element_hover.opacity(0.5))) - }) - .overflow_hidden() - .child(tool_icon) - .child(if has_location { - h_flex() - .id(("open-tool-call-location", entry_ix)) - .w_full() - .map(|this| { - if use_card_layout { - this.text_color(cx.theme().colors().text) - } else { - this.text_color(cx.theme().colors().text_muted) - } - }) - .child(self.render_markdown( - tool_call.label.clone(), - MarkdownStyle { - prevent_mouse_interaction: true, - ..default_markdown_style(false, true, window, cx) - }, - )) - .tooltip(Tooltip::text("Jump to File")) - .on_click(cx.listener(move |this, _, window, cx| { - this.open_tool_call_location(entry_ix, 0, window, cx); - })) - .into_any_element() - } else { - h_flex() - .w_full() - .child(self.render_markdown( - tool_call.label.clone(), - default_markdown_style(false, true, window, cx), - )) - .into_any() + .gap_1() + .justify_between() + .when(use_card_layout, |this| { + this.p_0p5() + .rounded_t(rems_from_px(5.)) + .bg(self.tool_card_header_bg(cx)) }) - .when(!has_location, |this| this.child(gradient_overlay)), - ) - .when(is_collapsible || failed_or_canceled, |this| { - this.child( - h_flex() - .px_1() - .gap_px() - .when(is_collapsible, |this| { - this.child( - Disclosure::new(("expand", entry_ix), is_open) - .opened_icon(IconName::ChevronUp) - .closed_icon(IconName::ChevronDown) - .visible_on_hover(&card_header_id) - .on_click(cx.listener({ - let id = tool_call.id.clone(); - move |this: &mut Self, _, _, cx: &mut Context| { - if is_open { - this.expanded_tool_calls.remove(&id); - } else { - this.expanded_tool_calls.insert(id.clone()); - } - cx.notify(); - } - })), + .child(self.render_tool_call_label( + entry_ix, + tool_call, + is_edit, + use_card_layout, + window, + cx, + )) + .when(is_collapsible || failed_or_canceled, |this| { + this.child( + h_flex() + .px_1() + .gap_px() + .when(is_collapsible, |this| { + this.child( + Disclosure::new(("expand", entry_ix), is_open) + .opened_icon(IconName::ChevronUp) + .closed_icon(IconName::ChevronDown) + .visible_on_hover(&card_header_id) + .on_click(cx.listener({ + let id = tool_call.id.clone(); + move |this: &mut Self, _, _, cx: &mut Context| { + if is_open { + this.expanded_tool_calls.remove(&id); + } else { + this.expanded_tool_calls.insert(id.clone()); + } + cx.notify(); + } + })), + ) + }) + .when(failed_or_canceled, |this| { + this.child( + Icon::new(IconName::Close) + .color(Color::Error) + .size(IconSize::Small), + ) + }), ) - }) - .when(failed_or_canceled, |this| { - this.child( - Icon::new(IconName::Close) - .color(Color::Error) - .size(IconSize::Small), - ) - }), - ) - }), - ) + }), + ) + } + }) .children(tool_output_display) } + fn render_tool_call_label( + &self, + entry_ix: usize, + tool_call: &ToolCall, + is_edit: bool, + use_card_layout: bool, + window: &Window, + cx: &Context, + ) -> Div { + let has_location = tool_call.locations.len() == 1; + + let tool_icon = if tool_call.kind == acp::ToolKind::Edit && has_location { + FileIcons::get_icon(&tool_call.locations[0].path, cx) + .map(Icon::from_path) + .unwrap_or(Icon::new(IconName::ToolPencil)) + } else { + Icon::new(match tool_call.kind { + acp::ToolKind::Read => IconName::ToolSearch, + acp::ToolKind::Edit => IconName::ToolPencil, + acp::ToolKind::Delete => IconName::ToolDeleteFile, + acp::ToolKind::Move => IconName::ArrowRightLeft, + acp::ToolKind::Search => IconName::ToolSearch, + acp::ToolKind::Execute => IconName::ToolTerminal, + acp::ToolKind::Think => IconName::ToolThink, + acp::ToolKind::Fetch => IconName::ToolWeb, + acp::ToolKind::SwitchMode => IconName::ArrowRightLeft, + acp::ToolKind::Other => IconName::ToolHammer, + }) + } + .size(IconSize::Small) + .color(Color::Muted); + + let gradient_overlay = { + div() + .absolute() + .top_0() + .right_0() + .w_12() + .h_full() + .map(|this| { + if use_card_layout { + this.bg(linear_gradient( + 90., + linear_color_stop(self.tool_card_header_bg(cx), 1.), + linear_color_stop(self.tool_card_header_bg(cx).opacity(0.2), 0.), + )) + } else { + this.bg(linear_gradient( + 90., + linear_color_stop(cx.theme().colors().panel_background, 1.), + linear_color_stop( + cx.theme().colors().panel_background.opacity(0.2), + 0., + ), + )) + } + }) + }; + + h_flex() + .relative() + .w_full() + .h(window.line_height() - px(2.)) + .text_size(self.tool_name_font_size()) + .gap_1p5() + .when(has_location || use_card_layout, |this| this.px_1()) + .when(has_location, |this| { + this.cursor(CursorStyle::PointingHand) + .rounded(rems_from_px(3.)) // Concentric border radius + .hover(|s| s.bg(cx.theme().colors().element_hover.opacity(0.5))) + }) + .overflow_hidden() + .child(tool_icon) + .child(if has_location { + h_flex() + .id(("open-tool-call-location", entry_ix)) + .w_full() + .map(|this| { + if use_card_layout { + this.text_color(cx.theme().colors().text) + } else { + this.text_color(cx.theme().colors().text_muted) + } + }) + .child(self.render_markdown( + tool_call.label.clone(), + MarkdownStyle { + prevent_mouse_interaction: true, + ..default_markdown_style(false, true, window, cx) + }, + )) + .tooltip(Tooltip::text("Jump to File")) + .on_click(cx.listener(move |this, _, window, cx| { + this.open_tool_call_location(entry_ix, 0, window, cx); + })) + .into_any_element() + } else { + h_flex() + .w_full() + .child(self.render_markdown( + tool_call.label.clone(), + default_markdown_style(false, true, window, cx), + )) + .into_any() + }) + .when(!is_edit, |this| this.child(gradient_overlay)) + } + fn render_tool_call_content( &self, entry_ix: usize,