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
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>
266 lines
8.1 KiB
Rust
266 lines
8.1 KiB
Rust
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)),
|
||
)
|
||
})
|
||
}
|
||
}
|