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_2()
 73                                    .child({
 74                                        let settings = ThemeSettings::get_global(cx);
 75                                        let text_style = TextStyle {
 76                                            color: cx.theme().colors().editor_foreground,
 77                                            font_family: settings.buffer_font.family.clone(),
 78                                            font_features: settings.buffer_font.features.clone(),
 79                                            font_size: font_size.into(),
 80                                            font_weight: FontWeight::NORMAL,
 81                                            font_style: FontStyle::Normal,
 82                                            line_height: line_height.into(),
 83                                            background_color: None,
 84                                            underline: None,
 85                                            strikethrough: None,
 86                                            white_space: WhiteSpace::Normal,
 87                                        };
 88
 89                                        EditorElement::new(
 90                                            &self.editor,
 91                                            EditorStyle {
 92                                                background: cx.theme().colors().editor_background,
 93                                                local_player: cx.theme().players().local(),
 94                                                text: text_style,
 95                                                ..Default::default()
 96                                            },
 97                                        )
 98                                    })
 99                                    .child(
100                                        h_flex()
101                                            .flex_none()
102                                            .gap_2()
103                                            .justify_between()
104                                            .w_full()
105                                            .child(
106                                                h_flex().gap_1().child(
107                                                    // IconButton/button
108                                                    // Toggle - if enabled, .selected(true).selected_style(IconButtonStyle::Filled)
109                                                    //
110                                                    // match status
111                                                    // Tooltip::with_meta("some label explaining project index + status", "click to enable")
112                                                    IconButton::new(
113                                                        "add-context",
114                                                        IconName::FileDoc,
115                                                    )
116                                                    .icon_color(Color::Muted),
117                                                ), // .child(
118                                                   //     IconButton::new(
119                                                   //         "add-context",
120                                                   //         IconName::Plus,
121                                                   //     )
122                                                   //     .icon_color(Color::Muted),
123                                                   // ),
124                                            )
125                                            .child(
126                                                Button::new("send-button", "Send")
127                                                    .style(ButtonStyle::Filled)
128                                                    .disabled(!self.can_submit)
129                                                    .on_click(|_, cx| {
130                                                        cx.dispatch_action(Box::new(Submit(
131                                                            SubmitMode::Codebase,
132                                                        )))
133                                                    })
134                                                    .tooltip(|cx| {
135                                                        Tooltip::for_action(
136                                                            "Submit message",
137                                                            &Submit(SubmitMode::Codebase),
138                                                            cx,
139                                                        )
140                                                    }),
141                                            ),
142                                    ),
143                            ),
144                    )
145                    .child(
146                        h_flex()
147                            .w_full()
148                            .justify_between()
149                            .child(self.model_selector)
150                            .children(self.tool_registry.status_views().iter().cloned()),
151                    ),
152            )
153    }
154}
155
156#[derive(IntoElement)]
157pub struct ModelSelector {
158    assistant_chat: WeakView<AssistantChat>,
159    model: String,
160}
161
162impl ModelSelector {
163    pub fn new(assistant_chat: WeakView<AssistantChat>, model: String) -> Self {
164        Self {
165            assistant_chat,
166            model,
167        }
168    }
169}
170
171impl RenderOnce for ModelSelector {
172    fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
173        popover_menu("model-switcher")
174            .menu(move |cx| {
175                ContextMenu::build(cx, |mut menu, cx| {
176                    for model in CompletionProvider::get(cx).available_models() {
177                        menu = menu.custom_entry(
178                            {
179                                let model = model.clone();
180                                move |_| Label::new(model.clone()).into_any_element()
181                            },
182                            {
183                                let assistant_chat = self.assistant_chat.clone();
184                                move |cx| {
185                                    _ = assistant_chat.update(cx, |assistant_chat, cx| {
186                                        assistant_chat.model = model.clone();
187                                        cx.notify();
188                                    });
189                                }
190                            },
191                        );
192                    }
193                    menu
194                })
195                .into()
196            })
197            .trigger(
198                ButtonLike::new("active-model")
199                    .child(
200                        h_flex()
201                            .w_full()
202                            .gap_0p5()
203                            .child(
204                                div()
205                                    .overflow_x_hidden()
206                                    .flex_grow()
207                                    .whitespace_nowrap()
208                                    .child(Label::new(self.model)),
209                            )
210                            .child(
211                                div().child(Icon::new(IconName::ChevronDown).color(Color::Muted)),
212                            ),
213                    )
214                    .style(ButtonStyle::Subtle)
215                    .tooltip(move |cx| Tooltip::text("Change Model", cx)),
216            )
217            .anchor(gpui::AnchorCorner::BottomRight)
218    }
219}