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>
198 lines
7.5 KiB
Rust
198 lines
7.5 KiB
Rust
use galaxy_core::ui::icons::Icon as GalaxyIcon;
|
|
use galaxy_core::ui::theme::color::internal_colors;
|
|
use galaxy_core::ui::theme::{Fill as GalaxyThemeFill, GalaxyTheme};
|
|
use galaxyui::elements::{
|
|
ChildAnchor, ConstrainedBox, Container, CornerRadius, Element, OffsetPositioning, ParentAnchor,
|
|
ParentElement, ParentOffsetBounds, Radius, Stack,
|
|
};
|
|
use pathfinder_color::ColorU;
|
|
use pathfinder_geometry::vector::vec2f;
|
|
|
|
use crate::ai::agent::conversation::ConversationStatus;
|
|
use crate::terminal::CLIAgent;
|
|
use crate::themes::theme::Fill as ThemeFill;
|
|
|
|
/// Sizing configuration for the icon circle and its status badge.
|
|
pub(crate) struct IconWithStatusSizing {
|
|
pub(crate) icon_size: f32,
|
|
pub(crate) padding: f32,
|
|
pub(crate) badge_icon_size: f32,
|
|
pub(crate) badge_padding: f32,
|
|
/// The overall constrained size for the stack.
|
|
/// When set, overrides the default `icon_size + padding * 2`.
|
|
pub(crate) overall_size_override: Option<f32>,
|
|
/// Offset of the status badge from the bottom-right corner of the circle.
|
|
/// Positive x pushes right, positive y pushes down.
|
|
pub(crate) badge_offset: (f32, f32),
|
|
}
|
|
|
|
/// What to render inside the circle.
|
|
pub(crate) enum IconWithStatusVariant {
|
|
/// A generic icon with a given color on an overlay background.
|
|
Neutral {
|
|
icon: GalaxyIcon,
|
|
icon_color: GalaxyThemeFill,
|
|
},
|
|
/// A pre-built icon element on an overlay background.
|
|
NeutralElement { icon_element: Box<dyn Element> },
|
|
/// An Oz agent icon on the theme background.
|
|
OzAgent {
|
|
status: Option<ConversationStatus>,
|
|
is_ambient: bool,
|
|
},
|
|
/// A CLI agent icon on the agent's brand color background.
|
|
CLIAgent {
|
|
agent: CLIAgent,
|
|
status: Option<ConversationStatus>,
|
|
},
|
|
}
|
|
|
|
/// Renders an icon inside a circle with an optional status badge overlay.
|
|
pub(crate) fn render_icon_with_status(
|
|
variant: IconWithStatusVariant,
|
|
sizing: &IconWithStatusSizing,
|
|
theme: &GalaxyTheme,
|
|
badge_ring_background: GalaxyThemeFill,
|
|
) -> Box<dyn Element> {
|
|
let sub_text = theme.sub_text_color(theme.background());
|
|
|
|
match variant {
|
|
IconWithStatusVariant::Neutral { icon, icon_color } => {
|
|
let inner = ConstrainedBox::new(icon.to_galaxyui_icon(icon_color).finish())
|
|
.with_width(sizing.icon_size)
|
|
.with_height(sizing.icon_size)
|
|
.finish();
|
|
Container::new(inner)
|
|
.with_uniform_padding(sizing.padding)
|
|
.with_background(internal_colors::fg_overlay_2(theme))
|
|
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(
|
|
(sizing.icon_size + sizing.padding * 2.) / 2.,
|
|
)))
|
|
.finish()
|
|
}
|
|
IconWithStatusVariant::NeutralElement { icon_element } => {
|
|
let inner = ConstrainedBox::new(icon_element)
|
|
.with_width(sizing.icon_size)
|
|
.with_height(sizing.icon_size)
|
|
.finish();
|
|
Container::new(inner)
|
|
.with_uniform_padding(sizing.padding)
|
|
.with_background(internal_colors::fg_overlay_2(theme))
|
|
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(
|
|
(sizing.icon_size + sizing.padding * 2.) / 2.,
|
|
)))
|
|
.finish()
|
|
}
|
|
IconWithStatusVariant::OzAgent { status, is_ambient } => {
|
|
let icon = if is_ambient {
|
|
GalaxyIcon::OzCloud
|
|
} else {
|
|
GalaxyIcon::Oz
|
|
};
|
|
let inner = ConstrainedBox::new(
|
|
icon.to_galaxyui_icon(theme.main_text_color(theme.background()))
|
|
.finish(),
|
|
)
|
|
.with_width(sizing.icon_size)
|
|
.with_height(sizing.icon_size)
|
|
.finish();
|
|
let circle = Container::new(inner)
|
|
.with_uniform_padding(sizing.padding)
|
|
.with_background(theme.background())
|
|
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(
|
|
(sizing.icon_size + sizing.padding * 2.) / 2.,
|
|
)))
|
|
.finish();
|
|
render_with_optional_status_badge(
|
|
circle,
|
|
status.as_ref(),
|
|
sizing,
|
|
theme,
|
|
badge_ring_background,
|
|
)
|
|
}
|
|
IconWithStatusVariant::CLIAgent { agent, status } => {
|
|
let brand_color = agent
|
|
.brand_color()
|
|
.unwrap_or(ColorU::new(100, 100, 100, 255));
|
|
let icon_color = agent.brand_icon_color();
|
|
let icon_element = agent
|
|
.icon()
|
|
.map(|icon| {
|
|
icon.to_galaxyui_icon(GalaxyThemeFill::Solid(icon_color))
|
|
.finish()
|
|
})
|
|
.unwrap_or_else(|| GalaxyIcon::Terminal.to_galaxyui_icon(sub_text).finish());
|
|
let inner = ConstrainedBox::new(icon_element)
|
|
.with_width(sizing.icon_size)
|
|
.with_height(sizing.icon_size)
|
|
.finish();
|
|
let circle = Container::new(inner)
|
|
.with_uniform_padding(sizing.padding)
|
|
.with_background(ThemeFill::Solid(brand_color))
|
|
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(
|
|
(sizing.icon_size + sizing.padding * 2.) / 2.,
|
|
)))
|
|
.finish();
|
|
render_with_optional_status_badge(
|
|
circle,
|
|
status.as_ref(),
|
|
sizing,
|
|
theme,
|
|
badge_ring_background,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Adds a status badge with a cutout ring to the bottom-right of the circle.
|
|
fn render_with_optional_status_badge(
|
|
circle: Box<dyn Element>,
|
|
status: Option<&ConversationStatus>,
|
|
sizing: &IconWithStatusSizing,
|
|
theme: &GalaxyTheme,
|
|
badge_ring_background: GalaxyThemeFill,
|
|
) -> Box<dyn Element> {
|
|
let Some(status) = status else {
|
|
return circle;
|
|
};
|
|
let (icon, color) = status.status_icon_and_color(theme);
|
|
let badge_icon =
|
|
ConstrainedBox::new(icon.to_galaxyui_icon(GalaxyThemeFill::Solid(color)).finish())
|
|
.with_width(sizing.badge_icon_size)
|
|
.with_height(sizing.badge_icon_size)
|
|
.finish();
|
|
let badge = Container::new(badge_icon)
|
|
.with_uniform_padding(sizing.badge_padding)
|
|
.with_corner_radius(CornerRadius::with_all(Radius::Percentage(50.)))
|
|
.finish();
|
|
// Cutout ring that visually separates the badge from the circle.
|
|
let badge_with_ring = Container::new(badge)
|
|
.with_uniform_padding(sizing.badge_padding)
|
|
.with_background(badge_ring_background)
|
|
.with_corner_radius(CornerRadius::with_all(Radius::Percentage(50.)))
|
|
.finish();
|
|
|
|
let circle_size = sizing.icon_size + sizing.padding * 2.;
|
|
let overall_size = sizing.overall_size_override.unwrap_or(circle_size);
|
|
let mut stack = Stack::new().with_child(
|
|
ConstrainedBox::new(circle)
|
|
.with_width(overall_size)
|
|
.with_height(overall_size)
|
|
.finish(),
|
|
);
|
|
stack.add_positioned_child(
|
|
badge_with_ring,
|
|
OffsetPositioning::offset_from_parent(
|
|
vec2f(sizing.badge_offset.0, sizing.badge_offset.1),
|
|
ParentOffsetBounds::ParentBySize,
|
|
ParentAnchor::BottomRight,
|
|
ChildAnchor::BottomRight,
|
|
),
|
|
);
|
|
ConstrainedBox::new(stack.finish())
|
|
.with_width(overall_size)
|
|
.with_height(overall_size)
|
|
.finish()
|
|
}
|