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);
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}