composer.rs

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