composer.rs

  1use assistant_tooling::ToolRegistry;
  2use client::User;
  3use editor::{Editor, EditorElement, EditorStyle};
  4use gpui::{AnyElement, FontStyle, FontWeight, TextStyle, View, WeakView, WhiteSpace};
  5use settings::Settings;
  6use std::sync::Arc;
  7use theme::ThemeSettings;
  8use ui::{popover_menu, prelude::*, Avatar, ButtonLike, ContextMenu, Tooltip};
  9
 10use crate::{AssistantChat, CompletionProvider, Submit, SubmitMode};
 11
 12#[derive(IntoElement)]
 13pub struct Composer {
 14    editor: View<Editor>,
 15    player: Option<Arc<User>>,
 16    can_submit: bool,
 17    tool_registry: Arc<ToolRegistry>,
 18    model_selector: AnyElement,
 19}
 20
 21impl Composer {
 22    pub fn new(
 23        editor: View<Editor>,
 24        player: Option<Arc<User>>,
 25        can_submit: bool,
 26        tool_registry: Arc<ToolRegistry>,
 27        model_selector: AnyElement,
 28    ) -> Self {
 29        Self {
 30            editor,
 31            player,
 32            can_submit,
 33            tool_registry,
 34            model_selector,
 35        }
 36    }
 37}
 38
 39impl RenderOnce for Composer {
 40    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
 41        let mut player_avatar = div().size(rems(20.0 / 16.0)).into_any_element();
 42        if let Some(player) = self.player.clone() {
 43            player_avatar = Avatar::new(player.avatar_uri.clone())
 44                .size(rems(20.0 / 16.0))
 45                .into_any_element();
 46        }
 47
 48        let font_size = rems(0.875);
 49        let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
 50
 51        h_flex()
 52            .w_full()
 53            .items_start()
 54            .mt_4()
 55            .gap_3()
 56            .child(player_avatar)
 57            .child(
 58                v_flex()
 59                    .size_full()
 60                    .gap_1()
 61                    .pr_4()
 62                    .child(
 63                        v_flex()
 64                            .w_full()
 65                            .p_4()
 66                            .bg(cx.theme().colors().editor_background)
 67                            .rounded_lg()
 68                            .child(
 69                                v_flex()
 70                                    .justify_between()
 71                                    .w_full()
 72                                    .gap_1()
 73                                    .min_h(line_height * 4 + px(74.0))
 74                                    .child({
 75                                        let settings = ThemeSettings::get_global(cx);
 76                                        let text_style = TextStyle {
 77                                            color: cx.theme().colors().editor_foreground,
 78                                            font_family: settings.buffer_font.family.clone(),
 79                                            font_features: settings.buffer_font.features.clone(),
 80                                            font_size: font_size.into(),
 81                                            font_weight: FontWeight::NORMAL,
 82                                            font_style: FontStyle::Normal,
 83                                            line_height: line_height.into(),
 84                                            background_color: None,
 85                                            underline: None,
 86                                            strikethrough: None,
 87                                            white_space: WhiteSpace::Normal,
 88                                        };
 89
 90                                        EditorElement::new(
 91                                            &self.editor,
 92                                            EditorStyle {
 93                                                background: cx.theme().colors().editor_background,
 94                                                local_player: cx.theme().players().local(),
 95                                                text: text_style,
 96                                                ..Default::default()
 97                                            },
 98                                        )
 99                                    })
100                                    .child(
101                                        h_flex()
102                                            .flex_none()
103                                            .gap_2()
104                                            .justify_between()
105                                            .w_full()
106                                            .child(
107                                                h_flex().gap_1().child(
108                                                    // IconButton/button
109                                                    // Toggle - if enabled, .selected(true).selected_style(IconButtonStyle::Filled)
110                                                    //
111                                                    // match status
112                                                    // Tooltip::with_meta("some label explaining project index + status", "click to enable")
113                                                    IconButton::new(
114                                                        "add-context",
115                                                        IconName::FileDoc,
116                                                    )
117                                                    .icon_color(Color::Muted),
118                                                ), // .child(
119                                                   //     IconButton::new(
120                                                   //         "add-context",
121                                                   //         IconName::Plus,
122                                                   //     )
123                                                   //     .icon_color(Color::Muted),
124                                                   // ),
125                                            )
126                                            .child(
127                                                Button::new("send-button", "Send")
128                                                    .style(ButtonStyle::Filled)
129                                                    .disabled(!self.can_submit)
130                                                    .on_click(|_, cx| {
131                                                        cx.dispatch_action(Box::new(Submit(
132                                                            SubmitMode::Codebase,
133                                                        )))
134                                                    })
135                                                    .tooltip(|cx| {
136                                                        Tooltip::for_action(
137                                                            "Submit message",
138                                                            &Submit(SubmitMode::Codebase),
139                                                            cx,
140                                                        )
141                                                    }),
142                                            ),
143                                    ),
144                            ),
145                    )
146                    .child(
147                        h_flex()
148                            .w_full()
149                            .justify_between()
150                            .child(self.model_selector)
151                            .children(self.tool_registry.status_views().iter().cloned()),
152                    ),
153            )
154    }
155}
156
157#[derive(IntoElement)]
158pub struct ModelSelector {
159    assistant_chat: WeakView<AssistantChat>,
160    model: String,
161}
162
163impl ModelSelector {
164    pub fn new(assistant_chat: WeakView<AssistantChat>, model: String) -> Self {
165        Self {
166            assistant_chat,
167            model,
168        }
169    }
170}
171
172impl RenderOnce for ModelSelector {
173    fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
174        popover_menu("model-switcher")
175            .menu(move |cx| {
176                ContextMenu::build(cx, |mut menu, cx| {
177                    for model in CompletionProvider::get(cx).available_models() {
178                        menu = menu.custom_entry(
179                            {
180                                let model = model.clone();
181                                move |_| Label::new(model.clone()).into_any_element()
182                            },
183                            {
184                                let assistant_chat = self.assistant_chat.clone();
185                                move |cx| {
186                                    _ = assistant_chat.update(cx, |assistant_chat, cx| {
187                                        assistant_chat.model = model.clone();
188                                        cx.notify();
189                                    });
190                                }
191                            },
192                        );
193                    }
194                    menu
195                })
196                .into()
197            })
198            .trigger(
199                ButtonLike::new("active-model")
200                    .child(
201                        h_flex()
202                            .w_full()
203                            .gap_0p5()
204                            .child(
205                                div()
206                                    .overflow_x_hidden()
207                                    .flex_grow()
208                                    .whitespace_nowrap()
209                                    .child(Label::new(self.model)),
210                            )
211                            .child(
212                                div().child(Icon::new(IconName::ChevronDown).color(Color::Muted)),
213                            ),
214                    )
215                    .style(ButtonStyle::Subtle)
216                    .tooltip(move |cx| Tooltip::text("Change Model", cx)),
217            )
218            .anchor(gpui::AnchorCorner::BottomRight)
219    }
220}