Files
galaxy/app/src/terminal/input/suggestions_mode_menu.rs
T
Ryan WardandClaude Opus 4.6 6f54e2cb30 v1.4.0: Auto-compact streaming, Bedrock summarization support, subagent orchestration, and Galaxy rebrand continuation
Major features:
- Auto-compact: triggers conversation summarization when context window >= 85%,
  compacts Bedrock message history to a summary pair, and tracks live context tokens
- Bedrock summarization: plumbs `is_summarization` flag through translator/client/response
  pipeline, handles SummarizeConversation input type, and marks `summarized` in metadata
- Session restore: rebuilds bedrock_message_history from persisted task messages via
  newly-public `convert_proto_message`, preventing empty history on reconnect
- Subagent orchestration: adds SubagentQuestion/Answer/CompletionSummary event types,
  parent-child question routing with depth limits, retry counting, and drain methods
- Summarization UI: inline SummarizationView in AI blocks with progress/finished states

Refactors:
- Rename WarpTheme → GalaxyTheme across ~100 files (rebrand continuation)
- Rename warp_home_config_dir → galaxy_home_config_dir and related path functions
- Predefined rules: replace "System Defined Rule #N" with descriptive names
  (e.g. "Correctness Over Speed", "Never Guess") and add lookup helpers
- Usage view: replace cumulative input/output token display with live context tokens,
  cache hit rate calculation, and separate cache read/write stats
- Telemetry: remove verbose doc comments, simplify trait definitions
- Facts view: simplify delete permission check (always allow local deletion)
- Remove warp_managed_paths_watcher.rs (dead code)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-21 11:59:37 -05:00

391 lines
15 KiB
Rust

//! Rendering logic for the input suggestions menu.
//!
//! This module contains all rendering functions for the various input suggestion modes:
//! - HistoryUp
//! - CompletionSuggestions
//! - StaticWorkflowEnumSuggestions
//! - DynamicWorkflowEnumSuggestions
use super::{
DynamicEnumSuggestionStatus, Input, InputAction, MenuPositioning, DYNAMIC_ENUM_FAILURE_MESSAGE,
DYNAMIC_ENUM_GENERATE_MESSAGE, DYNAMIC_ENUM_HORIZONTAL_TEXT_PADDING,
DYNAMIC_ENUM_MENU_HEIGHT_OFFSET, DYNAMIC_ENUM_MENU_PADDING, DYNAMIC_ENUM_NO_RESULTS_MESSAGE,
DYNAMIC_ENUM_PENDING_MESSAGE, DYNAMIC_ENUM_RUN_MESSAGE, HISTORY_DETAILS_VIEW_WIDTH_REQUIREMENT,
RUN_DYNAMIC_ENUM_COMMAND_KEYSTROKE, TERMINAL_VIEW_PADDING_LEFT,
};
use crate::appearance::Appearance;
use crate::input_suggestions::{
DETAILS_PANEL_MARGIN, DETAILS_PANEL_PADDING, HISTORY_DETAILS_PANEL_WIDTH,
LABEL_PADDING as InputSuggestionsLabelPadding,
};
use crate::themes::theme::GalaxyTheme;
use galaxyui::elements::{
Align, Border, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, DragBarSide,
DropShadow, Element, Empty, Flex, ParentElement, Radius, Resizable, Shrinkable,
SizeConstraintCondition, SizeConstraintSwitch, Text,
};
use galaxyui::presenter::ChildView;
use galaxyui::ui_components::components::{Coords, UiComponent, UiComponentStyles};
enum SuggestionsResizeConfig {
WidthAndHeight,
WidthOnly,
HeightOnly,
}
impl Input {
pub(super) fn render_history_up_menu(
&self,
appearance: &Appearance,
menu_positioning: MenuPositioning,
) -> Box<dyn Element> {
let theme = appearance.theme();
let radius = Radius::Pixels(6.);
let corner_radius = match menu_positioning {
MenuPositioning::AboveInputBox => CornerRadius::with_top(radius),
MenuPositioning::BelowInputBox => CornerRadius::with_bottom(radius),
};
let input_suggestions_border = 2.0;
let margin =
*TERMINAL_VIEW_PADDING_LEFT - input_suggestions_border - InputSuggestionsLabelPadding;
let content = ChildView::new(&self.input_suggestions).finish();
SizeConstraintSwitch::new(
Flex::row()
.with_children([
Shrinkable::new(
1.,
self.render_suggestions_container(
margin,
corner_radius,
theme,
SuggestionsResizeConfig::HeightOnly,
menu_positioning,
content,
),
)
.finish(),
ConstrainedBox::new(Empty::new().finish())
.with_width(
HISTORY_DETAILS_PANEL_WIDTH
+ DETAILS_PANEL_MARGIN * 2.
+ DETAILS_PANEL_PADDING * 2.,
)
.finish(),
])
.finish(),
vec![(
SizeConstraintCondition::WidthLessThan(HISTORY_DETAILS_VIEW_WIDTH_REQUIREMENT),
self.render_suggestions_container(
margin,
corner_radius,
theme,
SuggestionsResizeConfig::HeightOnly,
menu_positioning,
ChildView::new(&self.input_suggestions).finish(),
),
)],
)
.finish()
}
pub(super) fn render_completion_suggestions_menu(
&self,
appearance: &Appearance,
menu_positioning: MenuPositioning,
) -> Box<dyn Element> {
let theme = appearance.theme();
let corner_radius = CornerRadius::with_all(Radius::Pixels(6.));
let content = ChildView::new(&self.input_suggestions).finish();
self.render_suggestions_container(
0.,
corner_radius,
theme,
SuggestionsResizeConfig::WidthAndHeight,
menu_positioning,
content,
)
}
pub(super) fn render_workflow_enum_suggestions_menu(
&self,
appearance: &Appearance,
menu_positioning: MenuPositioning,
) -> Box<dyn Element> {
let theme = appearance.theme();
let corner_radius = CornerRadius::with_all(Radius::Pixels(6.));
// Same styling as CompletionSuggestions
let margin = 0.;
let content = ChildView::new(&self.input_suggestions).finish();
self.render_suggestions_container(
margin,
corner_radius,
theme,
SuggestionsResizeConfig::WidthAndHeight,
menu_positioning,
content,
)
}
pub(super) fn render_dynamic_workflow_enum_menu(
&self,
appearance: &Appearance,
menu_positioning: MenuPositioning,
command: String,
status: DynamicEnumSuggestionStatus,
suggestions: &[String],
) -> Box<dyn Element> {
let theme = appearance.theme();
let corner_radius = CornerRadius::with_all(Radius::Pixels(6.));
let (content, resize_config) = match status {
DynamicEnumSuggestionStatus::Unapproved => (
self.render_dynamic_enum_command_approval(command, appearance),
SuggestionsResizeConfig::WidthOnly,
),
DynamicEnumSuggestionStatus::Pending => (
self.render_dynamic_enum_status_message(DYNAMIC_ENUM_PENDING_MESSAGE, appearance),
SuggestionsResizeConfig::WidthAndHeight,
),
DynamicEnumSuggestionStatus::Failure => (
self.render_dynamic_enum_status_message(DYNAMIC_ENUM_FAILURE_MESSAGE, appearance),
SuggestionsResizeConfig::WidthAndHeight,
),
DynamicEnumSuggestionStatus::Success if suggestions.is_empty() => (
self.render_dynamic_enum_status_message(
DYNAMIC_ENUM_NO_RESULTS_MESSAGE,
appearance,
),
SuggestionsResizeConfig::WidthAndHeight,
),
DynamicEnumSuggestionStatus::Success => (
ChildView::new(&self.input_suggestions).finish(),
SuggestionsResizeConfig::WidthAndHeight,
),
};
self.render_suggestions_container(
0.,
corner_radius,
theme,
resize_config,
menu_positioning,
content,
)
}
/// Renders the suggestions container with configurable resize behavior.
fn render_suggestions_container(
&self,
margin: f32,
corner_radius: CornerRadius,
theme: &GalaxyTheme,
resize_config: SuggestionsResizeConfig,
menu_positioning: MenuPositioning,
content: Box<dyn Element>,
) -> Box<dyn Element> {
// Inner container for the visual menu content (background, border, shadow)
// This is wrapped by Resizable so the resize handles align with the border.
let inner_container = Container::new(content)
.with_background(theme.surface_2())
.with_corner_radius(corner_radius)
.with_drop_shadow(DropShadow::default())
.with_border(Border::all(1.0).with_border_fill(theme.outline()))
.finish();
// Apply width resizing based on config
let horizontal_resizable = match resize_config {
SuggestionsResizeConfig::WidthAndHeight | SuggestionsResizeConfig::WidthOnly => {
let width_handle_for_end = self.completions_menu_resizable_width.clone();
Resizable::new(
self.completions_menu_resizable_width.clone(),
inner_container,
)
.with_dragbar_side(DragBarSide::Right)
.with_dragbar_offset(7.0)
.with_bounds_callback(Box::new(|window_size| (200.0, window_size.x())))
.on_resize(move |ctx, _| {
ctx.notify();
})
.on_end_resizing(move |ctx, _| {
let new_width = width_handle_for_end
.lock()
.expect("width handle lock poisoned")
.size();
ctx.dispatch_typed_action(InputAction::UpdateCompletionsMenuWidth(new_width));
})
.finish()
}
SuggestionsResizeConfig::HeightOnly => inner_container,
};
// Apply height resizing based on config
// Skip vertical resizing for command approval to show full command because we want users
// to see the entire command they are about to execute.
let resizable_element = match resize_config {
SuggestionsResizeConfig::WidthOnly => horizontal_resizable,
SuggestionsResizeConfig::WidthAndHeight | SuggestionsResizeConfig::HeightOnly => {
let dragbar_side = match menu_positioning {
MenuPositioning::AboveInputBox => DragBarSide::Top,
MenuPositioning::BelowInputBox => DragBarSide::Bottom,
};
let height_handle_for_end = self.completions_menu_resizable_height.clone();
Resizable::new(
self.completions_menu_resizable_height.clone(),
horizontal_resizable,
)
.with_dragbar_side(dragbar_side)
.with_bounds_callback(Box::new(|window_size| (100.0, window_size.y())))
.on_resize(move |ctx, _| {
ctx.notify();
})
.on_end_resizing(move |ctx, _| {
let new_height = height_handle_for_end
.lock()
.expect("height handle lock poisoned")
.size();
ctx.dispatch_typed_action(InputAction::UpdateCompletionsMenuHeight(new_height));
})
.finish()
}
};
Container::new(resizable_element)
.with_margin_top(12.0)
.with_margin_left(margin)
.with_margin_right(margin)
.finish()
}
/// Renders the command approval UI for dynamic enum suggestions.
fn render_dynamic_enum_command_approval(
&self,
command: String,
appearance: &Appearance,
) -> Box<dyn Element> {
let theme = appearance.theme();
let keybinding_style = UiComponentStyles {
height: Some(15.),
font_size: Some(appearance.ui_font_size()),
margin: Some(Coords {
left: 2.,
right: 2.,
..Default::default()
}),
..Default::default()
};
Flex::column()
.with_child(
ConstrainedBox::new(
Container::new(
Text::new_inline(
String::from(DYNAMIC_ENUM_GENERATE_MESSAGE),
appearance.ui_font_family(),
appearance.ui_font_size(),
)
.with_color(theme.main_text_color(theme.surface_2()).into_solid())
.finish(),
)
.with_uniform_padding(DYNAMIC_ENUM_MENU_PADDING)
.finish(),
)
.with_max_height(appearance.ui_font_size() + DYNAMIC_ENUM_MENU_HEIGHT_OFFSET)
.finish(),
)
.with_child(
Container::new(
Text::new(
command.clone(),
appearance.monospace_font_family(),
appearance.monospace_font_size(),
)
.with_color(theme.main_text_color(theme.background()).into_solid())
.finish(),
)
.with_uniform_padding(DYNAMIC_ENUM_MENU_PADDING)
.with_background(theme.background())
.finish(),
)
.with_child(
ConstrainedBox::new(
Flex::row()
.with_child(
Shrinkable::new(
1.,
Container::new(
appearance
.ui_builder()
.keyboard_shortcut(&RUN_DYNAMIC_ENUM_COMMAND_KEYSTROKE)
.with_style(keybinding_style)
.build()
.finish(),
)
.with_uniform_padding(DYNAMIC_ENUM_HORIZONTAL_TEXT_PADDING)
.finish(),
)
.finish(),
)
.with_child(
Shrinkable::new(
1.,
Container::new(
Text::new_inline(
String::from(DYNAMIC_ENUM_RUN_MESSAGE),
appearance.ui_font_family(),
appearance.ui_font_size(),
)
.with_color(theme.sub_text_color(theme.surface_2()).into())
.finish(),
)
.with_uniform_padding(DYNAMIC_ENUM_HORIZONTAL_TEXT_PADDING)
.finish(),
)
.finish(),
)
.with_cross_axis_alignment(CrossAxisAlignment::Center)
.finish(),
)
.with_max_height(appearance.ui_font_size() + DYNAMIC_ENUM_MENU_HEIGHT_OFFSET)
.finish(),
)
.finish()
}
/// Renders a status message for dynamic enum suggestions.
fn render_dynamic_enum_status_message(
&self,
message: &str,
appearance: &Appearance,
) -> Box<dyn Element> {
let theme = appearance.theme();
ConstrainedBox::new(
Align::new(
Container::new(
Text::new_inline(
String::from(message),
appearance.monospace_font_family(),
appearance.monospace_font_size(),
)
.with_color(theme.sub_text_color(theme.surface_2()).into())
.finish(),
)
.with_uniform_padding(DYNAMIC_ENUM_HORIZONTAL_TEXT_PADDING)
.finish(),
)
.finish(),
)
.with_max_height(appearance.monospace_font_size() + DYNAMIC_ENUM_MENU_HEIGHT_OFFSET)
.finish()
}
}