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}