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}