Files
zed/crates/agent_ui/src/ui/model_selector_components.rs
T
Mohamad KhaniandClaude Fable 5 b72a46db68
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled
logiguard fork: GPUI xdg-activation keyboard-focus serial fix
Single-commit orphan branch: full zed-industries/zed @ 8c74db0 source tree
with a 3-file patch applied (no upstream history).

Patch (crates/gpui_linux/src/linux/wayland/):
  - serial.rs: add SerialKind::KeyboardEnter
  - client.rs: store wl_keyboard.enter serial; add latest_serial_of()
  - window.rs: activate() uses keyboard-enter serial (Mutter focus gate)

Mutter honors window activation only when the token carries the keyboard-
focus serial from wl_keyboard.enter; GPUI used a stale mouse-press serial.
See docs/tray-window-focus-wayland.md in logiguard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 02:22:17 +03:30

266 lines
8.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use gpui::{Action, ClickEvent, FocusHandle, prelude::*};
use ui::{Chip, ElevationIndex, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*};
use zed_actions::agent::ToggleModelSelector;
use crate::CycleFavoriteModels;
enum ModelIcon {
Name(IconName),
Path(SharedString),
}
#[derive(IntoElement)]
pub struct ModelSelectorHeader {
title: SharedString,
has_border: bool,
}
impl ModelSelectorHeader {
pub fn new(title: impl Into<SharedString>, has_border: bool) -> Self {
Self {
title: title.into(),
has_border,
}
}
}
impl RenderOnce for ModelSelectorHeader {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
div()
.px_2()
.pb_1()
.when(self.has_border, |this| {
this.mt_1()
.pt_2()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
})
.child(
Label::new(self.title)
.size(LabelSize::XSmall)
.color(Color::Muted),
)
}
}
#[derive(IntoElement)]
pub struct ModelSelectorListItem {
index: usize,
title: SharedString,
icon: Option<ModelIcon>,
is_selected: bool,
is_focused: bool,
is_latest: bool,
is_favorite: bool,
on_toggle_favorite: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
cost_info: Option<SharedString>,
}
impl ModelSelectorListItem {
pub fn new(index: usize, title: impl Into<SharedString>) -> Self {
Self {
index,
title: title.into(),
icon: None,
is_selected: false,
is_focused: false,
is_latest: false,
is_favorite: false,
on_toggle_favorite: None,
cost_info: None,
}
}
pub fn icon(mut self, icon: IconName) -> Self {
self.icon = Some(ModelIcon::Name(icon));
self
}
pub fn icon_path(mut self, path: SharedString) -> Self {
self.icon = Some(ModelIcon::Path(path));
self
}
pub fn is_selected(mut self, is_selected: bool) -> Self {
self.is_selected = is_selected;
self
}
pub fn is_focused(mut self, is_focused: bool) -> Self {
self.is_focused = is_focused;
self
}
pub fn is_latest(mut self, is_latest: bool) -> Self {
self.is_latest = is_latest;
self
}
pub fn is_favorite(mut self, is_favorite: bool) -> Self {
self.is_favorite = is_favorite;
self
}
pub fn on_toggle_favorite(
mut self,
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> Self {
self.on_toggle_favorite = Some(Box::new(handler));
self
}
pub fn cost_info(mut self, cost_info: Option<SharedString>) -> Self {
self.cost_info = cost_info;
self
}
}
impl RenderOnce for ModelSelectorListItem {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let model_icon_color = if self.is_selected {
Color::Accent
} else {
Color::Muted
};
let is_favorite = self.is_favorite;
ListItem::new(self.index)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(self.is_focused)
.child(
h_flex()
.w_full()
.gap_1p5()
.when_some(self.icon, |this, icon| {
this.child(
match icon {
ModelIcon::Name(icon_name) => Icon::new(icon_name),
ModelIcon::Path(icon_path) => Icon::from_external_svg(icon_path),
}
.color(model_icon_color)
.size(IconSize::Small),
)
})
.child(Label::new(self.title).truncate())
.when(self.is_latest, |parent| parent.child(Chip::new("Latest")))
.when_some(self.cost_info, |this, cost_info| {
let tooltip_text = if cost_info.ends_with('×') {
format!("Cost Multiplier: {}", cost_info)
} else if cost_info.contains('$') {
format!("Cost per Million Tokens: {}", cost_info)
} else {
format!("Cost: {}", cost_info)
};
this.child(Chip::new(cost_info).tooltip(Tooltip::text(tooltip_text)))
}),
)
.end_slot(div().pr_2().when(self.is_selected, |this| {
this.child(Icon::new(IconName::Check).color(Color::Accent))
}))
.end_slot_on_hover(div().pr_1p5().when_some(self.on_toggle_favorite, {
|this, handle_click| {
let (icon, color, tooltip) = if is_favorite {
(IconName::StarFilled, Color::Accent, "Unfavorite Model")
} else {
(IconName::Star, Color::Default, "Favorite Model")
};
this.child(
IconButton::new(("toggle-favorite", self.index), icon)
.layer(ElevationIndex::ElevatedSurface)
.icon_color(color)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text(tooltip))
.on_click(move |event, window, cx| (handle_click)(event, window, cx)),
)
}
}))
}
}
#[derive(IntoElement)]
pub struct ModelSelectorFooter {
action: Box<dyn Action>,
focus_handle: FocusHandle,
}
impl ModelSelectorFooter {
pub fn new(action: Box<dyn Action>, focus_handle: FocusHandle) -> Self {
Self {
action,
focus_handle,
}
}
}
impl RenderOnce for ModelSelectorFooter {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let action = self.action;
let focus_handle = self.focus_handle;
h_flex()
.w_full()
.p_1p5()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.child(
Button::new("configure", "Configure")
.full_width()
.style(ButtonStyle::Outlined)
.key_binding(
KeyBinding::for_action_in(action.as_ref(), &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(move |_, window, cx| {
window.dispatch_action(action.boxed_clone(), cx);
}),
)
}
}
#[derive(IntoElement)]
pub struct ModelSelectorTooltip {
show_cycle_row: bool,
}
impl ModelSelectorTooltip {
pub fn new() -> Self {
Self {
show_cycle_row: true,
}
}
pub fn show_cycle_row(mut self, show: bool) -> Self {
self.show_cycle_row = show;
self
}
}
impl RenderOnce for ModelSelectorTooltip {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
v_flex()
.gap_1()
.child(
h_flex()
.gap_2()
.justify_between()
.child(Label::new("Change Model"))
.child(KeyBinding::for_action(&ToggleModelSelector, cx)),
)
.when(self.show_cycle_row, |this| {
this.child(
h_flex()
.pt_1()
.gap_2()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.justify_between()
.child(Label::new("Cycle Favorited Models"))
.child(KeyBinding::for_action(&CycleFavoriteModels, cx)),
)
})
}
}