From 2015498831106452a52c6e0594222554e0d36664 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Wed, 5 Aug 2026 01:08:26 -0500 Subject: [PATCH] ACP work --- .../ai/blocklist/block/view_impl/output.rs | 131 ++++++++---------- app/src/ai/blocklist/inline_action/mod.rs | 1 + .../inline_action/requested_command.rs | 32 ++--- .../ai/blocklist/inline_action/tool_pane.rs | 42 ++++++ 4 files changed, 110 insertions(+), 96 deletions(-) create mode 100644 app/src/ai/blocklist/inline_action/tool_pane.rs diff --git a/app/src/ai/blocklist/block/view_impl/output.rs b/app/src/ai/blocklist/block/view_impl/output.rs index 10441bc2..94d9aa16 100644 --- a/app/src/ai/blocklist/block/view_impl/output.rs +++ b/app/src/ai/blocklist/block/view_impl/output.rs @@ -22,7 +22,7 @@ use galaxy_core::ui::theme::color::internal_colors; use galaxy_util::local_or_remote_path::LocalOrRemotePath; use galaxyui::elements::new_scrollable::SingleAxisConfig; use galaxyui::elements::{ - Align, Border, ChildAnchor, ChildView, ConstrainedBox, Container, CornerRadius, + Align, Border, ChildAnchor, ChildView, Clipped, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Empty, Expanded, Fill, Flex, FormattedTextElement, Hoverable, MainAxisAlignment, MainAxisSize, NewScrollable, OffsetPositioning, ParentAnchor, ParentElement, ParentOffsetBounds, Radius, Shrinkable, Stack, Text, Wrap, @@ -85,18 +85,22 @@ use crate::ai::blocklist::inline_action::ask_user_question_view::AskUserQuestion use crate::ai::blocklist::inline_action::aws_bedrock_credentials_error::AwsBedrockCredentialsErrorView; use crate::ai::blocklist::inline_action::create_or_edit_document::CreateOrEditDocumentAction; use crate::ai::blocklist::inline_action::inline_action_header::{ - HeaderConfig, InteractionMode, INLINE_ACTION_HEADER_VERTICAL_PADDING, + ExpandedConfig, HeaderConfig, InteractionMode, INLINE_ACTION_HEADER_VERTICAL_PADDING, INLINE_ACTION_HORIZONTAL_PADDING, }; use crate::ai::blocklist::inline_action::inline_action_icons::{self, icon_size}; use crate::ai::blocklist::inline_action::requested_action::{ render_requested_action_body_text, render_requested_action_row_for_text, RenderableAction, }; -use crate::ai::blocklist::inline_action::requested_command::RequestedCommand; +use crate::ai::blocklist::inline_action::requested_command::{ + format_command_text, RequestedCommand, REQUESTED_COMMAND_BODY_VERTICAL_PADDING, + VIEWING_COMMAND_DETAIL_MESSAGE, +}; use crate::ai::blocklist::inline_action::run_agents_card_view::RunAgentsCardView; use crate::ai::blocklist::inline_action::search_codebase::SearchCodebaseView; use crate::ai::blocklist::inline_action::suggested_unit_tests::SuggestedUnitTestsView; use crate::ai::blocklist::inline_action::summarization::SummarizationView; +use crate::ai::blocklist::inline_action::tool_pane::render_tool_pane_shell; use crate::ai::blocklist::inline_action::web_fetch::WebFetchView; use crate::ai::blocklist::inline_action::web_search::WebSearchView; use crate::ai::blocklist::keyboard_navigable_buttons::KeyboardNavigableButtons; @@ -1287,7 +1291,6 @@ fn render_runtime_activity( let state = props.collapsible_block_states.get(&output_message.id)?; let appearance = Appearance::as_ref(app); let theme = appearance.theme(); - let text_color = blended_colors::text_main(theme, theme.background()); let output = activity .output .as_deref() @@ -1296,55 +1299,50 @@ fn render_runtime_activity( state.expansion_state, CollapsibleExpansionState::Expanded { .. } ); - let mut content = Flex::column().with_cross_axis_alignment(CrossAxisAlignment::Stretch); - - let title = Text::new( - activity.title.clone(), - appearance.monospace_font_family(), - appearance.monospace_font_size(), - ) - .with_color(text_color) - .with_selectable(false) - .finish(); - if output.is_some() { - let chevron = if is_expanded { - Icon::ChevronDown - } else { - Icon::ChevronRight - }; - let icon_sz = icon_size(app); - let message_id = output_message.id.clone(); - let mouse_state = state.expansion_toggle_mouse_state.clone(); - let header = Hoverable::new(mouse_state, move |_| { - Flex::row() - .with_cross_axis_alignment(CrossAxisAlignment::Center) - .with_child(Shrinkable::new(1., title).finish()) - .with_child( - Container::new( - ConstrainedBox::new(chevron.to_galaxyui_icon(text_color.into()).finish()) - .with_width(icon_sz) - .with_height(icon_sz) - .finish(), - ) - .with_margin_left(6.) - .finish(), - ) - .finish() - }) - .with_cursor(Cursor::PointingHand) - .on_click(move |ctx, _, _| { - ctx.dispatch_typed_action(AIBlockAction::ToggleCollapsibleBlockExpanded( - message_id.clone(), - )); - }); - content.add_child( - Flex::row() - .with_child(Shrinkable::new(1., header.finish()).finish()) - .finish(), - ); + let icon = match activity.status.as_ref() { + Some(RuntimeActivityStatus::Pending) => icons::pending_icon(appearance), + Some(RuntimeActivityStatus::InProgress) => icons::yellow_running_icon(appearance), + Some(RuntimeActivityStatus::Completed) => inline_action_icons::green_check_icon(appearance), + Some(RuntimeActivityStatus::Failed) => inline_action_icons::red_x_icon(appearance), + Some(RuntimeActivityStatus::Other(_)) | None => icons::gray_circle_icon(appearance), + }; + let title = if is_expanded { + VIEWING_COMMAND_DETAIL_MESSAGE.to_owned() } else { - content.add_child(title); + format_command_text(&activity.title) + }; + let mut header = HeaderConfig::new(title, app) + .with_selectable_text() + .with_icon(icon) + .with_corner_radius_override(if is_expanded && output.is_some() { + CornerRadius::with_top(Radius::Pixels(8.)) + } else { + CornerRadius::with_all(Radius::Pixels(8.)) + }); + if !is_expanded { + header = header.with_font_family(appearance.monospace_font_family()); } + if output.is_some() { + let message_id = output_message.id.clone(); + let command = activity.title.clone(); + let expansion = + ExpandedConfig::new(is_expanded, state.expansion_toggle_mouse_state.clone()) + .with_toggle_callback(move |ctx| { + ctx.dispatch_typed_action(AIBlockAction::ToggleCollapsibleBlockExpanded( + message_id.clone(), + )); + }) + .with_right_click_callback(move |ctx| { + ctx.dispatch_typed_action(AIBlockAction::StoreRightClickedCommand { + command: command.clone(), + }); + }); + header = header.with_interaction_mode(InteractionMode::ManuallyExpandable(expansion)); + } + + let mut content = Flex::column() + .with_cross_axis_alignment(CrossAxisAlignment::Stretch) + .with_child(Clipped::new(header.render(app)).finish()); if let Some(output) = output { let body = render_requested_action_body_text( @@ -1364,31 +1362,18 @@ fn render_runtime_activity( is_streaming, 320., ) { - content.add_child(Container::new(scrollable).with_margin_top(12.).finish()); + content.add_child( + Container::new(scrollable) + .with_horizontal_padding(INLINE_ACTION_HORIZONTAL_PADDING) + .with_vertical_padding(REQUESTED_COMMAND_BODY_VERTICAL_PADDING) + .with_background(theme.background()) + .with_corner_radius(CornerRadius::with_bottom(Radius::Pixels(8.))) + .finish(), + ); } } - let icon = match activity.status.as_ref() { - Some(RuntimeActivityStatus::Completed) => { - inline_action_icons::green_check_icon(appearance).finish() - } - Some(RuntimeActivityStatus::Failed) => inline_action_icons::red_x_icon(appearance).finish(), - Some(RuntimeActivityStatus::Pending) - | Some(RuntimeActivityStatus::InProgress) - | Some(RuntimeActivityStatus::Other(_)) - | None => galaxyui::elements::Icon::new( - Icon::ClockRefresh.into(), - internal_colors::neutral_5(appearance.theme()), - ) - .finish(), - }; - - Some( - RenderableAction::new_with_element(content.finish(), app) - .with_icon(icon) - .render(app) - .finish(), - ) + Some(render_tool_pane_shell(content.finish(), false, false, app)) } fn should_render_stopped_output(props: Props, app: &AppContext) -> bool { diff --git a/app/src/ai/blocklist/inline_action/mod.rs b/app/src/ai/blocklist/inline_action/mod.rs index c4ad5b1d..6569d319 100644 --- a/app/src/ai/blocklist/inline_action/mod.rs +++ b/app/src/ai/blocklist/inline_action/mod.rs @@ -16,5 +16,6 @@ pub(super) mod search_codebase; pub(crate) mod search_results_common; pub(crate) mod suggested_unit_tests; pub(super) mod summarization; +pub(crate) mod tool_pane; pub(super) mod web_fetch; pub(super) mod web_search; diff --git a/app/src/ai/blocklist/inline_action/requested_command.rs b/app/src/ai/blocklist/inline_action/requested_command.rs index f0ab771b..c3696783 100644 --- a/app/src/ai/blocklist/inline_action/requested_command.rs +++ b/app/src/ai/blocklist/inline_action/requested_command.rs @@ -36,13 +36,13 @@ use crate::ai::blocklist::block::cli_controller::{ use crate::ai::blocklist::block::view_impl::output::action_icon; use crate::ai::blocklist::block::view_impl::{ render_autonomy_checkbox_setting_speedbump_footer, render_citation, render_citation_chips, - CONTENT_HORIZONTAL_PADDING, CONTENT_ITEM_VERTICAL_MARGIN, }; use crate::ai::blocklist::block::{AIBlockAction, AutonomySettingSpeedbump}; use crate::ai::blocklist::inline_action::inline_action_header::{ ExpandedConfig, HeaderConfig, InteractionMode, RightClickConfig, INLINE_ACTION_HORIZONTAL_PADDING, }; +use crate::ai::blocklist::inline_action::tool_pane::render_tool_pane_shell; use crate::ai::blocklist::model::{AIBlockModel, AIBlockModelHelper}; use crate::ai::blocklist::{ AIBlock, BlocklistAIActionEvent, BlocklistAIActionModel, BlocklistAIHistoryModel, @@ -1600,14 +1600,9 @@ impl View for RequestedCommandView { content.add_child(Clipped::new(footer).finish()); } - let border_color = if action_status + let has_highlighted_border = action_status .as_ref() - .is_some_and(|status| status.is_blocked()) - { - theme.accent() - } else { - theme.surface_2() - }; + .is_some_and(|status| status.is_blocked()); // If the requested command is expanded above a terminal block or // the next exchange flows directly after, remove bottom margin for @@ -1637,21 +1632,12 @@ impl View for RequestedCommandView { })) && !is_input_pinned_to_top); - let container = Container::new(content.finish()) - .with_margin_left(if action_status.is_some_and(|status| status.is_blocked()) { - CONTENT_HORIZONTAL_PADDING - } else { - CONTENT_HORIZONTAL_PADDING + icon_size(app) + 16. - }) - .with_margin_right(CONTENT_HORIZONTAL_PADDING) - .with_margin_bottom(if should_remove_bottom_margin { - 0. - } else { - CONTENT_ITEM_VERTICAL_MARGIN - }) - .with_corner_radius(CornerRadius::with_all(Radius::Pixels(8.))) - .with_border(Border::all(1.).with_border_fill(border_color)) - .finish(); + let container = render_tool_pane_shell( + content.finish(), + has_highlighted_border, + should_remove_bottom_margin, + app, + ); let mut root_stack = Stack::new(); root_stack.add_child(container); diff --git a/app/src/ai/blocklist/inline_action/tool_pane.rs b/app/src/ai/blocklist/inline_action/tool_pane.rs new file mode 100644 index 00000000..135dc3a0 --- /dev/null +++ b/app/src/ai/blocklist/inline_action/tool_pane.rs @@ -0,0 +1,42 @@ +use galaxy_core::ui::appearance::Appearance; +use warpui::elements::{Border, Container, CornerRadius, ParentElement, Radius}; +use warpui::{AppContext, Element, SingletonEntity}; + +use super::inline_action_icons::icon_size; +use crate::ai::blocklist::block::view_impl::{ + CONTENT_HORIZONTAL_PADDING, CONTENT_ITEM_VERTICAL_MARGIN, +}; + +/// Renders the shared outer shell used by native and runtime-owned tool panes. +/// +/// Callers own execution and body content. This function owns the pane geometry +/// and theme treatment so display-only runtimes cannot drift from native tools. +pub(crate) fn render_tool_pane_shell( + content: Box, + has_highlighted_border: bool, + should_remove_bottom_margin: bool, + app: &AppContext, +) -> Box { + let theme = Appearance::as_ref(app).theme(); + let border_color = if has_highlighted_border { + theme.accent() + } else { + theme.surface_2() + }; + + Container::new(content) + .with_margin_left(if has_highlighted_border { + CONTENT_HORIZONTAL_PADDING + } else { + CONTENT_HORIZONTAL_PADDING + icon_size(app) + 16. + }) + .with_margin_right(CONTENT_HORIZONTAL_PADDING) + .with_margin_bottom(if should_remove_bottom_margin { + 0. + } else { + CONTENT_ITEM_VERTICAL_MARGIN + }) + .with_corner_radius(CornerRadius::with_all(Radius::Pixels(8.))) + .with_border(Border::all(1.).with_border_fill(border_color)) + .finish() +}