Improve agent and provider model selection UI

This commit is contained in:
2026-08-09 16:19:08 -05:00
parent 170a87e981
commit fa43f723a5
3 changed files with 147 additions and 63 deletions
@@ -7,8 +7,8 @@ use galaxy_core::ui::color::blend::Blend;
use galaxy_core::ui::theme::color::internal_colors; use galaxy_core::ui::theme::color::internal_colors;
use galaxyui::elements::{ use galaxyui::elements::{
Align, Border, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Dismiss, Align, Border, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Dismiss,
DropShadow, Element, Flex, Hoverable, MainAxisAlignment, MainAxisSize, MouseStateHandle, DropShadow, Element, Expanded, Flex, Hoverable, MainAxisAlignment, MainAxisSize,
ParentElement, Radius, Shrinkable, Text, MouseStateHandle, ParentElement, Radius, Shrinkable, Text,
}; };
use galaxyui::fonts::{Properties, Weight}; use galaxyui::fonts::{Properties, Weight};
use galaxyui::keymap::{FixedBinding, Keystroke}; use galaxyui::keymap::{FixedBinding, Keystroke};
@@ -22,7 +22,7 @@ use crate::appearance::Appearance;
use crate::ui_components::icons::Icon; use crate::ui_components::icons::Icon;
// Modal dimensions based on Figma design. // Modal dimensions based on Figma design.
const MODAL_WIDTH: f32 = 440.; const MODAL_WIDTH: f32 = 680.;
const DIALOG_CORNER_RADIUS: f32 = 8.; const DIALOG_CORNER_RADIUS: f32 = 8.;
const HEADER_PADDING_TOP: f32 = 24.; const HEADER_PADDING_TOP: f32 = 24.;
@@ -40,6 +40,7 @@ const OPTIONS_VERTICAL_GAP: f32 = 8.;
const AVATAR_SIZE: f32 = 48.; const AVATAR_SIZE: f32 = 48.;
const AVATAR_ICON_SIZE: f32 = 24.; const AVATAR_ICON_SIZE: f32 = 24.;
const OPTION_HEIGHT: f32 = 136.;
const TITLE_FONT_SIZE: f32 = 16.; const TITLE_FONT_SIZE: f32 = 16.;
const OPTION_TITLE_FONT_SIZE: f32 = 14.; const OPTION_TITLE_FONT_SIZE: f32 = 14.;
@@ -292,21 +293,25 @@ impl AgentTypeSelector {
) )
.finish(); .finish();
Container::new( ConstrainedBox::new(
Flex::row() Container::new(
.with_cross_axis_alignment(CrossAxisAlignment::Center) Flex::row()
.with_spacing(OPTION_GAP) .with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_child(avatar) .with_spacing(OPTION_GAP)
.with_child(Shrinkable::new(1., text_content).finish()) .with_child(avatar)
.finish(), .with_child(Shrinkable::new(1., text_content).finish())
.finish(),
)
.with_padding_left(OPTION_PADDING_HORIZONTAL)
.with_padding_right(OPTION_PADDING_HORIZONTAL)
.with_padding_top(OPTION_PADDING_VERTICAL)
.with_padding_bottom(OPTION_PADDING_VERTICAL)
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(OPTION_CORNER_RADIUS)))
.with_border(Border::all(1.).with_border_color(border_color))
.with_background(background)
.finish(),
) )
.with_padding_left(OPTION_PADDING_HORIZONTAL) .with_height(OPTION_HEIGHT)
.with_padding_right(OPTION_PADDING_HORIZONTAL)
.with_padding_top(OPTION_PADDING_VERTICAL)
.with_padding_bottom(OPTION_PADDING_VERTICAL)
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(OPTION_CORNER_RADIUS)))
.with_border(Border::all(1.).with_border_color(border_color))
.with_background(background)
.finish() .finish()
}) })
.with_cursor(Cursor::PointingHand) .with_cursor(Cursor::PointingHand)
@@ -353,11 +358,11 @@ impl AgentTypeSelector {
appearance, appearance,
); );
let options = Flex::column() let options = Flex::row()
.with_cross_axis_alignment(CrossAxisAlignment::Stretch) .with_cross_axis_alignment(CrossAxisAlignment::Stretch)
.with_spacing(OPTIONS_VERTICAL_GAP) .with_spacing(OPTIONS_VERTICAL_GAP)
.with_child(cloud_agent_option) .with_child(Expanded::new(1., cloud_agent_option).finish())
.with_child(local_agent_option) .with_child(Expanded::new(1., local_agent_option).finish())
.finish(); .finish();
let body = Container::new(options) let body = Container::new(options)
@@ -93,6 +93,17 @@ fn make_item_fields<A: Action + Clone>(
Icon::Aws Icon::Aws
} else if is_custom_router { } else if is_custom_router {
Icon::Dataflow Icon::Dataflow
} else if matches!(llm.provider, crate::ai::llms::LLMProvider::Acp) {
let model_id = llm.id.as_str().to_ascii_lowercase();
if model_id.contains("claude") {
Icon::ClaudeLogo
} else if model_id.contains("gemini") {
Icon::GeminiLogo
} else if model_id.contains("codex") {
Icon::OpenAILogo
} else {
Icon::Terminal
}
} else { } else {
llm.provider.icon().unwrap_or(Icon::Oz) llm.provider.icon().unwrap_or(Icon::Oz)
}; };
+111 -43
View File
@@ -1,3 +1,5 @@
use galaxy_cli::agent::Harness;
use galaxy_core::ui::theme::Fill;
use galaxyui::clipboard::ClipboardContent; use galaxyui::clipboard::ClipboardContent;
use galaxyui::elements::{ use galaxyui::elements::{
Border, ChildView, ClippedScrollStateHandle, ClippedScrollable, ConstrainedBox, Container, Border, ChildView, ClippedScrollStateHandle, ClippedScrollable, ConstrainedBox, Container,
@@ -12,9 +14,11 @@ use galaxyui::ui_components::switch::SwitchStateHandle;
use galaxyui::{ use galaxyui::{
AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext, ViewHandle, AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext, ViewHandle,
}; };
use pathfinder_color::ColorU;
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
use crate::ai::chatgpt_auth::{ChatGPTAuthModel, ChatGPTAuthModelEvent, ChatGPTAuthState}; use crate::ai::chatgpt_auth::{ChatGPTAuthModel, ChatGPTAuthModelEvent, ChatGPTAuthState};
use crate::ai::harness_display;
use crate::ai::llms::{merge_discovered_provider_models, LLMPreferences}; use crate::ai::llms::{merge_discovered_provider_models, LLMPreferences};
use crate::appearance::Appearance; use crate::appearance::Appearance;
use crate::editor::{ use crate::editor::{
@@ -34,6 +38,7 @@ const MODAL_WIDTH: f32 = 900.;
const MODAL_HEIGHT: f32 = 700.; const MODAL_HEIGHT: f32 = 700.;
const BODY_HEIGHT: f32 = 630.; const BODY_HEIGHT: f32 = 630.;
const INPUT_FONT_SIZE: f32 = 12.; const INPUT_FONT_SIZE: f32 = 12.;
const MODEL_LOGO_SIZE: f32 = 20.;
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ProviderSetupStep { enum ProviderSetupStep {
@@ -1539,6 +1544,49 @@ impl ProviderSetupModalBody {
.finish() .finish()
} }
fn model_logo(&self) -> (Icon, ColorU) {
match self.provider_type {
ProviderSetupProviderType::ChatGPTSubscription
| ProviderSetupProviderType::OpenAICompatible => {
(Icon::OpenAILogo, crate::terminal::cli_agent::OPENAI_COLOR)
}
ProviderSetupProviderType::Anthropic => {
(Icon::ClaudeLogo, crate::ai::blocklist::CLAUDE_ORANGE)
}
ProviderSetupProviderType::Gemini | ProviderSetupProviderType::VertexAI => {
(Icon::GeminiLogo, crate::terminal::cli_agent::GEMINI_BLUE)
}
ProviderSetupProviderType::Bedrock => {
(Icon::BedrockLogo, ColorU::new(255, 153, 0, 255))
}
ProviderSetupProviderType::Acp => {
let agent = self.draft_acp.agent_id.to_ascii_lowercase();
let harness = if agent.contains("claude") {
Harness::Claude
} else if agent.contains("gemini") {
Harness::Gemini
} else if agent.contains("codex") {
Harness::Codex
} else {
Harness::Unknown
};
(
harness_display::icon_for(harness),
harness_display::brand_color(harness)
.unwrap_or(ColorU::new(128, 128, 128, 255)),
)
}
}
}
fn render_model_logo(&self) -> Box<dyn galaxyui::Element> {
let (icon, color) = self.model_logo();
ConstrainedBox::new(icon.to_galaxyui_icon(Fill::Solid(color)).finish())
.with_width(MODEL_LOGO_SIZE)
.with_height(MODEL_LOGO_SIZE)
.finish()
}
fn render_model_table_header(&self, appearance: &Appearance) -> Box<dyn galaxyui::Element> { fn render_model_table_header(&self, appearance: &Appearance) -> Box<dyn galaxyui::Element> {
let header = |label: &str| { let header = |label: &str| {
Text::new(label.to_owned(), appearance.ui_font_family(), 11.) Text::new(label.to_owned(), appearance.ui_font_family(), 11.)
@@ -1630,6 +1678,12 @@ impl ProviderSetupModalBody {
.finish(), .finish(),
) )
.finish(); .finish();
let model_info = Flex::row()
.with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_spacing(10.)
.with_child(self.render_model_logo())
.with_child(model_info)
.finish();
let context_input = appearance let context_input = appearance
.ui_builder() .ui_builder()
@@ -1689,26 +1743,33 @@ impl ProviderSetupModalBody {
.models .models
.iter() .iter()
.map(|model| { .map(|model| {
Flex::column() Flex::row()
.with_spacing(2.) .with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_spacing(10.)
.with_child(self.render_model_logo())
.with_child( .with_child(
Text::new( Flex::column()
model.display_name.clone(), .with_spacing(2.)
appearance.ui_font_family(), .with_child(
INPUT_FONT_SIZE, Text::new(
) model.display_name.clone(),
.with_color(appearance.theme().active_ui_text_color().into()) appearance.ui_font_family(),
.finish(), INPUT_FONT_SIZE,
) )
.with_child( .with_color(appearance.theme().active_ui_text_color().into())
Text::new( .finish(),
model.model_id.clone(), )
appearance.monospace_font_family(), .with_child(
10., Text::new(
) model.model_id.clone(),
.with_color(appearance.theme().nonactive_ui_text_color().into()) appearance.monospace_font_family(),
.soft_wrap(true) 10.,
.finish(), )
.with_color(appearance.theme().nonactive_ui_text_color().into())
.soft_wrap(true)
.finish(),
)
.finish(),
) )
.finish() .finish()
}) })
@@ -1747,31 +1808,38 @@ impl ProviderSetupModalBody {
.map(|value| value.name.as_str()) .map(|value| value.name.as_str())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "); .join(", ");
Flex::column() Flex::row()
.with_spacing(2.) .with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_spacing(10.)
.with_child(self.render_model_logo())
.with_child( .with_child(
Text::new( Flex::column()
option.name.clone(), .with_spacing(2.)
appearance.ui_font_family(), .with_child(
INPUT_FONT_SIZE, Text::new(
) option.name.clone(),
.with_color(appearance.theme().active_ui_text_color().into()) appearance.ui_font_family(),
.with_style(Properties::default().weight(Weight::Bold)) INPUT_FONT_SIZE,
.finish(), )
) .with_color(appearance.theme().active_ui_text_color().into())
.with_child( .with_style(Properties::default().weight(Weight::Bold))
Text::new( .finish(),
if values.is_empty() { )
option.current_value.to_string() .with_child(
} else { Text::new(
values if values.is_empty() {
}, option.current_value.to_string()
appearance.monospace_font_family(), } else {
10., values
) },
.with_color(appearance.theme().nonactive_ui_text_color().into()) appearance.monospace_font_family(),
.soft_wrap(true) 10.,
.finish(), )
.with_color(appearance.theme().nonactive_ui_text_color().into())
.soft_wrap(true)
.finish(),
)
.finish(),
) )
.finish() .finish()
}) })