message_editor.rs

  1use editor::{Editor, EditorElement, EditorStyle};
  2use gpui::{AppContext, FocusableView, Model, TextStyle, View};
  3use language_model::LanguageModelRegistry;
  4use settings::Settings;
  5use theme::ThemeSettings;
  6use ui::{prelude::*, ButtonLike, ElevationIndex, KeyBinding};
  7
  8use crate::thread::{RequestKind, Thread};
  9use crate::Chat;
 10
 11pub struct MessageEditor {
 12    thread: Model<Thread>,
 13    editor: View<Editor>,
 14}
 15
 16impl MessageEditor {
 17    pub fn new(thread: Model<Thread>, cx: &mut ViewContext<Self>) -> Self {
 18        Self {
 19            thread,
 20            editor: cx.new_view(|cx| {
 21                let mut editor = Editor::auto_height(80, cx);
 22                editor.set_placeholder_text("Ask anything…", cx);
 23
 24                editor
 25            }),
 26        }
 27    }
 28
 29    fn chat(&mut self, _: &Chat, cx: &mut ViewContext<Self>) {
 30        self.send_to_model(RequestKind::Chat, cx);
 31    }
 32
 33    fn send_to_model(
 34        &mut self,
 35        request_kind: RequestKind,
 36        cx: &mut ViewContext<Self>,
 37    ) -> Option<()> {
 38        let provider = LanguageModelRegistry::read_global(cx).active_provider();
 39        if provider
 40            .as_ref()
 41            .map_or(false, |provider| provider.must_accept_terms(cx))
 42        {
 43            cx.notify();
 44            return None;
 45        }
 46
 47        let model_registry = LanguageModelRegistry::read_global(cx);
 48        let model = model_registry.active_model()?;
 49
 50        let user_message = self.editor.update(cx, |editor, cx| {
 51            let text = editor.text(cx);
 52            editor.clear(cx);
 53            text
 54        });
 55
 56        self.thread.update(cx, |thread, cx| {
 57            thread.insert_user_message(user_message);
 58            let request = thread.to_completion_request(request_kind, cx);
 59            thread.stream_completion(request, model, cx)
 60        });
 61
 62        None
 63    }
 64}
 65
 66impl FocusableView for MessageEditor {
 67    fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
 68        self.editor.focus_handle(cx)
 69    }
 70}
 71
 72impl Render for MessageEditor {
 73    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
 74        let font_size = TextSize::Default.rems(cx);
 75        let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
 76        let focus_handle = self.editor.focus_handle(cx);
 77
 78        v_flex()
 79            .key_context("MessageEditor")
 80            .on_action(cx.listener(Self::chat))
 81            .size_full()
 82            .gap_2()
 83            .p_2()
 84            .bg(cx.theme().colors().editor_background)
 85            .child({
 86                let settings = ThemeSettings::get_global(cx);
 87                let text_style = TextStyle {
 88                    color: cx.theme().colors().editor_foreground,
 89                    font_family: settings.ui_font.family.clone(),
 90                    font_features: settings.ui_font.features.clone(),
 91                    font_size: font_size.into(),
 92                    font_weight: settings.ui_font.weight,
 93                    line_height: line_height.into(),
 94                    ..Default::default()
 95                };
 96
 97                EditorElement::new(
 98                    &self.editor,
 99                    EditorStyle {
100                        background: cx.theme().colors().editor_background,
101                        local_player: cx.theme().players().local(),
102                        text: text_style,
103                        ..Default::default()
104                    },
105                )
106            })
107            .child(
108                h_flex()
109                    .justify_between()
110                    .child(
111                        h_flex().child(
112                            Button::new("add-context", "Add Context")
113                                .style(ButtonStyle::Filled)
114                                .icon(IconName::Plus)
115                                .icon_position(IconPosition::Start),
116                        ),
117                    )
118                    .child(
119                        h_flex()
120                            .gap_2()
121                            .child(Button::new("codebase", "Codebase").style(ButtonStyle::Filled))
122                            .child(Label::new("or"))
123                            .child(
124                                ButtonLike::new("chat")
125                                    .style(ButtonStyle::Filled)
126                                    .layer(ElevationIndex::ModalSurface)
127                                    .child(Label::new("Chat"))
128                                    .children(
129                                        KeyBinding::for_action_in(&Chat, &focus_handle, cx)
130                                            .map(|binding| binding.into_any_element()),
131                                    )
132                                    .on_click(move |_event, cx| {
133                                        focus_handle.dispatch_action(&Chat, cx);
134                                    }),
135                            ),
136                    ),
137            )
138    }
139}