message_editor.rs

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