message_editor.rs

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