composer.rs

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