Improve agent and provider model selection UI
This commit is contained in:
@@ -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)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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()
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user