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, TextSize, Tooltip};
 10
 11#[derive(IntoElement)]
 12pub struct Composer {
 13    editor: View<Editor>,
 14    project_index_button: 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: 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().child(self.project_index_button.clone())
 36    }
 37
 38    fn render_attachment_tools(&mut self, _cx: &mut WindowContext) -> impl IntoElement {
 39        h_flex().children(
 40            self.active_file_button
 41                .clone()
 42                .map(|view| view.into_any_element()),
 43        )
 44    }
 45}
 46
 47impl RenderOnce for Composer {
 48    fn render(mut self, cx: &mut WindowContext) -> impl IntoElement {
 49        let font_size = TextSize::Default.rems(cx);
 50        let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
 51
 52        h_flex()
 53            .p(Spacing::Small.rems(cx))
 54            .w_full()
 55            .items_start()
 56            .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}