composer.rs

  1use crate::{ui::ProjectIndexButton, AssistantChat, CompletionProvider};
  2use editor::{Editor, EditorElement, EditorStyle};
  3use gpui::{AnyElement, FontStyle, FontWeight, TextStyle, View, WeakView, WhiteSpace};
  4use settings::Settings;
  5use theme::ThemeSettings;
  6use ui::{popover_menu, prelude::*, ButtonLike, ContextMenu, Tooltip};
  7
  8#[derive(IntoElement)]
  9pub struct Composer {
 10    editor: View<Editor>,
 11    project_index_button: Option<View<ProjectIndexButton>>,
 12    model_selector: AnyElement,
 13}
 14
 15impl Composer {
 16    pub fn new(
 17        editor: View<Editor>,
 18        project_index_button: Option<View<ProjectIndexButton>>,
 19        model_selector: AnyElement,
 20    ) -> Self {
 21        Self {
 22            editor,
 23            project_index_button,
 24            model_selector,
 25        }
 26    }
 27
 28    fn render_tools(&mut self, _cx: &mut WindowContext) -> impl IntoElement {
 29        h_flex().children(
 30            self.project_index_button
 31                .clone()
 32                .map(|view| view.into_any_element()),
 33        )
 34    }
 35}
 36
 37impl RenderOnce for Composer {
 38    fn render(mut self, cx: &mut WindowContext) -> impl IntoElement {
 39        let font_size = rems(0.875);
 40        let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
 41
 42        h_flex().w_full().items_start().mt_2().child(
 43            v_flex().size_full().gap_1().child(
 44                v_flex()
 45                    .w_full()
 46                    .p_3()
 47                    .bg(cx.theme().colors().editor_background)
 48                    .rounded_lg()
 49                    .child(
 50                        v_flex()
 51                            .justify_between()
 52                            .w_full()
 53                            .gap_2()
 54                            .child({
 55                                let settings = ThemeSettings::get_global(cx);
 56                                let text_style = TextStyle {
 57                                    color: cx.theme().colors().editor_foreground,
 58                                    font_family: settings.buffer_font.family.clone(),
 59                                    font_features: settings.buffer_font.features.clone(),
 60                                    font_size: font_size.into(),
 61                                    font_weight: FontWeight::NORMAL,
 62                                    font_style: FontStyle::Normal,
 63                                    line_height: line_height.into(),
 64                                    background_color: None,
 65                                    underline: None,
 66                                    strikethrough: None,
 67                                    white_space: WhiteSpace::Normal,
 68                                };
 69
 70                                EditorElement::new(
 71                                    &self.editor,
 72                                    EditorStyle {
 73                                        background: cx.theme().colors().editor_background,
 74                                        local_player: cx.theme().players().local(),
 75                                        text: text_style,
 76                                        ..Default::default()
 77                                    },
 78                                )
 79                            })
 80                            .child(
 81                                h_flex()
 82                                    .flex_none()
 83                                    .gap_2()
 84                                    .justify_between()
 85                                    .w_full()
 86                                    .child(h_flex().gap_1().child(self.render_tools(cx)))
 87                                    .child(h_flex().gap_1().child(self.model_selector)),
 88                            ),
 89                    ),
 90            ),
 91        )
 92    }
 93}
 94
 95#[derive(IntoElement)]
 96pub struct ModelSelector {
 97    assistant_chat: WeakView<AssistantChat>,
 98    model: String,
 99}
100
101impl ModelSelector {
102    pub fn new(assistant_chat: WeakView<AssistantChat>, model: String) -> Self {
103        Self {
104            assistant_chat,
105            model,
106        }
107    }
108}
109
110impl RenderOnce for ModelSelector {
111    fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
112        popover_menu("model-switcher")
113            .menu(move |cx| {
114                ContextMenu::build(cx, |mut menu, cx| {
115                    for model in CompletionProvider::get(cx).available_models() {
116                        menu = menu.custom_entry(
117                            {
118                                let model = model.clone();
119                                move |_| Label::new(model.clone()).into_any_element()
120                            },
121                            {
122                                let assistant_chat = self.assistant_chat.clone();
123                                move |cx| {
124                                    _ = assistant_chat.update(cx, |assistant_chat, cx| {
125                                        assistant_chat.model = model.clone();
126                                        cx.notify();
127                                    });
128                                }
129                            },
130                        );
131                    }
132                    menu
133                })
134                .into()
135            })
136            .trigger(
137                ButtonLike::new("active-model")
138                    .child(
139                        h_flex()
140                            .w_full()
141                            .gap_0p5()
142                            .child(
143                                div()
144                                    .overflow_x_hidden()
145                                    .flex_grow()
146                                    .whitespace_nowrap()
147                                    .child(
148                                        Label::new(self.model)
149                                            .size(LabelSize::Small)
150                                            .color(Color::Muted),
151                                    ),
152                            )
153                            .child(
154                                div().child(
155                                    Icon::new(IconName::ChevronDown)
156                                        .color(Color::Muted)
157                                        .size(IconSize::XSmall),
158                                ),
159                            ),
160                    )
161                    .style(ButtonStyle::Subtle)
162                    .tooltip(move |cx| Tooltip::text("Change Model", cx)),
163            )
164            .anchor(gpui::AnchorCorner::BottomRight)
165    }
166}