message_editor.rs

  1use std::sync::Arc;
  2
  3use crate::assistant_model_selector::ModelType;
  4use collections::HashSet;
  5use editor::actions::MoveUp;
  6use editor::{
  7    ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement, EditorMode, EditorStyle,
  8    MultiBuffer,
  9};
 10use file_icons::FileIcons;
 11use fs::Fs;
 12use gpui::{
 13    Animation, AnimationExt, App, DismissEvent, Entity, Focusable, Subscription, TextStyle,
 14    WeakEntity, linear_color_stop, linear_gradient, point, pulsating_between,
 15};
 16use language::{Buffer, Language};
 17use language_model::{ConfiguredModel, LanguageModelRegistry};
 18use language_model_selector::ToggleModelSelector;
 19use multi_buffer;
 20use project::Project;
 21use settings::Settings;
 22use std::time::Duration;
 23use theme::ThemeSettings;
 24use ui::{Disclosure, KeyBinding, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
 25use util::ResultExt as _;
 26use workspace::Workspace;
 27
 28use crate::assistant_model_selector::AssistantModelSelector;
 29use crate::context_picker::{ConfirmBehavior, ContextPicker, ContextPickerCompletionProvider};
 30use crate::context_store::{ContextStore, refresh_context_store_text};
 31use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
 32use crate::profile_selector::ProfileSelector;
 33use crate::thread::{RequestKind, Thread, TokenUsageRatio};
 34use crate::thread_store::ThreadStore;
 35use crate::{
 36    AgentDiff, Chat, ChatMode, ExpandMessageEditor, NewThread, OpenAgentDiff, RemoveAllContext,
 37    ToggleContextPicker, ToggleProfileSelector,
 38};
 39
 40pub struct MessageEditor {
 41    thread: Entity<Thread>,
 42    editor: Entity<Editor>,
 43    #[allow(dead_code)]
 44    workspace: WeakEntity<Workspace>,
 45    project: Entity<Project>,
 46    context_store: Entity<ContextStore>,
 47    context_strip: Entity<ContextStrip>,
 48    context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
 49    inline_context_picker: Entity<ContextPicker>,
 50    inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
 51    model_selector: Entity<AssistantModelSelector>,
 52    profile_selector: Entity<ProfileSelector>,
 53    edits_expanded: bool,
 54    editor_is_expanded: bool,
 55    waiting_for_summaries_to_send: bool,
 56    _subscriptions: Vec<Subscription>,
 57}
 58
 59const MAX_EDITOR_LINES: usize = 10;
 60
 61impl MessageEditor {
 62    pub fn new(
 63        fs: Arc<dyn Fs>,
 64        workspace: WeakEntity<Workspace>,
 65        context_store: Entity<ContextStore>,
 66        thread_store: WeakEntity<ThreadStore>,
 67        thread: Entity<Thread>,
 68        window: &mut Window,
 69        cx: &mut Context<Self>,
 70    ) -> Self {
 71        let context_picker_menu_handle = PopoverMenuHandle::default();
 72        let inline_context_picker_menu_handle = PopoverMenuHandle::default();
 73        let model_selector_menu_handle = PopoverMenuHandle::default();
 74
 75        let language = Language::new(
 76            language::LanguageConfig {
 77                completion_query_characters: HashSet::from_iter(['.', '-', '_', '@']),
 78                ..Default::default()
 79            },
 80            None,
 81        );
 82
 83        let editor = cx.new(|cx| {
 84            let buffer = cx.new(|cx| Buffer::local("", cx).with_language(Arc::new(language), cx));
 85            let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 86            let mut editor = Editor::new(
 87                editor::EditorMode::AutoHeight {
 88                    max_lines: MAX_EDITOR_LINES,
 89                },
 90                buffer,
 91                None,
 92                window,
 93                cx,
 94            );
 95            editor.set_placeholder_text("Ask anything, @ to mention, ↑ to select", cx);
 96            editor.set_show_indent_guides(false, cx);
 97            editor.set_context_menu_options(ContextMenuOptions {
 98                min_entries_visible: 12,
 99                max_entries_visible: 12,
100                placement: Some(ContextMenuPlacement::Above),
101            });
102            editor
103        });
104
105        let editor_entity = editor.downgrade();
106        editor.update(cx, |editor, _| {
107            editor.set_completion_provider(Some(Box::new(ContextPickerCompletionProvider::new(
108                workspace.clone(),
109                context_store.downgrade(),
110                Some(thread_store.clone()),
111                editor_entity,
112            ))));
113        });
114
115        let inline_context_picker = cx.new(|cx| {
116            ContextPicker::new(
117                workspace.clone(),
118                Some(thread_store.clone()),
119                context_store.downgrade(),
120                ConfirmBehavior::Close,
121                window,
122                cx,
123            )
124        });
125
126        let context_strip = cx.new(|cx| {
127            ContextStrip::new(
128                context_store.clone(),
129                workspace.clone(),
130                Some(thread_store.clone()),
131                context_picker_menu_handle.clone(),
132                SuggestContextKind::File,
133                window,
134                cx,
135            )
136        });
137
138        let subscriptions = vec![
139            cx.subscribe_in(
140                &inline_context_picker,
141                window,
142                Self::handle_inline_context_picker_event,
143            ),
144            cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event),
145        ];
146
147        Self {
148            editor: editor.clone(),
149            project: thread.read(cx).project().clone(),
150            thread,
151            workspace,
152            context_store,
153            context_strip,
154            context_picker_menu_handle,
155            inline_context_picker,
156            inline_context_picker_menu_handle,
157            model_selector: cx.new(|cx| {
158                AssistantModelSelector::new(
159                    fs.clone(),
160                    model_selector_menu_handle,
161                    editor.focus_handle(cx),
162                    ModelType::Default,
163                    window,
164                    cx,
165                )
166            }),
167            edits_expanded: false,
168            editor_is_expanded: false,
169            waiting_for_summaries_to_send: false,
170            profile_selector: cx
171                .new(|cx| ProfileSelector::new(fs, thread_store, editor.focus_handle(cx), cx)),
172            _subscriptions: subscriptions,
173        }
174    }
175
176    fn toggle_chat_mode(&mut self, _: &ChatMode, _window: &mut Window, cx: &mut Context<Self>) {
177        cx.notify();
178    }
179
180    fn expand_message_editor(
181        &mut self,
182        _: &ExpandMessageEditor,
183        _window: &mut Window,
184        cx: &mut Context<Self>,
185    ) {
186        self.set_editor_is_expanded(!self.editor_is_expanded, cx);
187    }
188
189    fn set_editor_is_expanded(&mut self, is_expanded: bool, cx: &mut Context<Self>) {
190        self.editor_is_expanded = is_expanded;
191        self.editor.update(cx, |editor, _| {
192            if self.editor_is_expanded {
193                editor.set_mode(EditorMode::Full {
194                    scale_ui_elements_with_buffer_font_size: false,
195                    show_active_line_background: false,
196                })
197            } else {
198                editor.set_mode(EditorMode::AutoHeight {
199                    max_lines: MAX_EDITOR_LINES,
200                })
201            }
202        });
203        cx.notify();
204    }
205
206    fn toggle_context_picker(
207        &mut self,
208        _: &ToggleContextPicker,
209        window: &mut Window,
210        cx: &mut Context<Self>,
211    ) {
212        self.context_picker_menu_handle.toggle(window, cx);
213    }
214    pub fn remove_all_context(
215        &mut self,
216        _: &RemoveAllContext,
217        _window: &mut Window,
218        cx: &mut Context<Self>,
219    ) {
220        self.context_store.update(cx, |store, _cx| store.clear());
221        cx.notify();
222    }
223
224    fn chat(&mut self, _: &Chat, window: &mut Window, cx: &mut Context<Self>) {
225        if self.is_editor_empty(cx) {
226            return;
227        }
228
229        if self.thread.read(cx).is_generating() {
230            return;
231        }
232
233        self.set_editor_is_expanded(false, cx);
234        self.send_to_model(RequestKind::Chat, window, cx);
235
236        cx.notify();
237    }
238
239    fn is_editor_empty(&self, cx: &App) -> bool {
240        self.editor.read(cx).text(cx).trim().is_empty()
241    }
242
243    fn is_model_selected(&self, cx: &App) -> bool {
244        LanguageModelRegistry::read_global(cx)
245            .default_model()
246            .is_some()
247    }
248
249    fn send_to_model(
250        &mut self,
251        request_kind: RequestKind,
252        window: &mut Window,
253        cx: &mut Context<Self>,
254    ) {
255        let model_registry = LanguageModelRegistry::read_global(cx);
256        let Some(ConfiguredModel { model, provider }) = model_registry.default_model() else {
257            return;
258        };
259
260        if provider.must_accept_terms(cx) {
261            cx.notify();
262            return;
263        }
264
265        let user_message = self.editor.update(cx, |editor, cx| {
266            let text = editor.text(cx);
267            editor.clear(window, cx);
268            text
269        });
270
271        let refresh_task =
272            refresh_context_store_text(self.context_store.clone(), &HashSet::default(), cx);
273
274        let thread = self.thread.clone();
275        let context_store = self.context_store.clone();
276        let git_store = self.project.read(cx).git_store().clone();
277        let checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
278
279        cx.spawn(async move |this, cx| {
280            let checkpoint = checkpoint.await.ok();
281            refresh_task.await;
282
283            thread
284                .update(cx, |thread, cx| {
285                    let context = context_store.read(cx).context().clone();
286                    thread.insert_user_message(user_message, context, checkpoint, cx);
287                })
288                .log_err();
289
290            if let Some(wait_for_summaries) = context_store
291                .update(cx, |context_store, cx| context_store.wait_for_summaries(cx))
292                .log_err()
293            {
294                this.update(cx, |this, cx| {
295                    this.waiting_for_summaries_to_send = true;
296                    cx.notify();
297                })
298                .log_err();
299
300                wait_for_summaries.await;
301
302                this.update(cx, |this, cx| {
303                    this.waiting_for_summaries_to_send = false;
304                    cx.notify();
305                })
306                .log_err();
307            }
308
309            // Send to model after summaries are done
310            thread
311                .update(cx, |thread, cx| {
312                    thread.send_to_model(model, request_kind, cx);
313                })
314                .log_err();
315        })
316        .detach();
317    }
318
319    fn handle_inline_context_picker_event(
320        &mut self,
321        _inline_context_picker: &Entity<ContextPicker>,
322        _event: &DismissEvent,
323        window: &mut Window,
324        cx: &mut Context<Self>,
325    ) {
326        let editor_focus_handle = self.editor.focus_handle(cx);
327        window.focus(&editor_focus_handle);
328    }
329
330    fn handle_context_strip_event(
331        &mut self,
332        _context_strip: &Entity<ContextStrip>,
333        event: &ContextStripEvent,
334        window: &mut Window,
335        cx: &mut Context<Self>,
336    ) {
337        match event {
338            ContextStripEvent::PickerDismissed
339            | ContextStripEvent::BlurredEmpty
340            | ContextStripEvent::BlurredDown => {
341                let editor_focus_handle = self.editor.focus_handle(cx);
342                window.focus(&editor_focus_handle);
343            }
344            ContextStripEvent::BlurredUp => {}
345        }
346    }
347
348    fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
349        if self.context_picker_menu_handle.is_deployed()
350            || self.inline_context_picker_menu_handle.is_deployed()
351        {
352            cx.propagate();
353        } else {
354            self.context_strip.focus_handle(cx).focus(window);
355        }
356    }
357
358    fn handle_review_click(&self, window: &mut Window, cx: &mut Context<Self>) {
359        AgentDiff::deploy(self.thread.clone(), self.workspace.clone(), window, cx).log_err();
360    }
361
362    fn handle_file_click(
363        &self,
364        buffer: Entity<Buffer>,
365        window: &mut Window,
366        cx: &mut Context<Self>,
367    ) {
368        if let Ok(diff) = AgentDiff::deploy(self.thread.clone(), self.workspace.clone(), window, cx)
369        {
370            let path_key = multi_buffer::PathKey::for_buffer(&buffer, cx);
371            diff.update(cx, |diff, cx| diff.move_to_path(path_key, window, cx));
372        }
373    }
374}
375
376impl Focusable for MessageEditor {
377    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
378        self.editor.focus_handle(cx)
379    }
380}
381
382impl Render for MessageEditor {
383    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
384        let font_size = TextSize::Small.rems(cx);
385        let line_height = font_size.to_pixels(window.rem_size()) * 1.5;
386
387        let focus_handle = self.editor.focus_handle(cx);
388        let focus_handle_clone = focus_handle.clone();
389        let inline_context_picker = self.inline_context_picker.clone();
390
391        let is_editor_expanded = self.editor_is_expanded;
392        let expand_icon = if is_editor_expanded {
393            IconName::Minimize
394        } else {
395            IconName::Maximize
396        };
397
398        let thread = self.thread.read(cx);
399        let is_generating = thread.is_generating();
400        let total_token_usage = thread.total_token_usage(cx);
401        let is_model_selected = self.is_model_selected(cx);
402        let is_editor_empty = self.is_editor_empty(cx);
403        let is_edit_changes_expanded = self.edits_expanded;
404
405        let action_log = self.thread.read(cx).action_log();
406        let changed_buffers = action_log.read(cx).changed_buffers(cx);
407        let changed_buffers_count = changed_buffers.len();
408
409        let editor_bg_color = cx.theme().colors().editor_background;
410        let border_color = cx.theme().colors().border;
411        let active_color = cx.theme().colors().element_selected;
412        let bg_edit_files_disclosure = editor_bg_color.blend(active_color.opacity(0.3));
413
414        v_flex()
415            .size_full()
416            .when(self.waiting_for_summaries_to_send, |parent| {
417                parent.child(
418                    h_flex().py_3().w_full().justify_center().child(
419                        h_flex()
420                            .flex_none()
421                            .px_2()
422                            .py_2()
423                            .bg(editor_bg_color)
424                            .border_1()
425                            .border_color(cx.theme().colors().border_variant)
426                            .rounded_lg()
427                            .shadow_md()
428                            .gap_1()
429                            .child(
430                                Icon::new(IconName::ArrowCircle)
431                                    .size(IconSize::XSmall)
432                                    .color(Color::Muted)
433                                    .with_animation(
434                                        "arrow-circle",
435                                        Animation::new(Duration::from_secs(2)).repeat(),
436                                        |icon, delta| {
437                                            icon.transform(gpui::Transformation::rotate(
438                                                gpui::percentage(delta),
439                                            ))
440                                        },
441                                    ),
442                            )
443                            .child(
444                                Label::new("Summarizing context…")
445                                    .size(LabelSize::XSmall)
446                                    .color(Color::Muted),
447                            ),
448                    ),
449                )
450            })
451            .when(changed_buffers_count > 0, |parent| {
452                parent.child(
453                    v_flex()
454                        .mx_2()
455                        .bg(bg_edit_files_disclosure)
456                        .border_1()
457                        .border_b_0()
458                        .border_color(border_color)
459                        .rounded_t_md()
460                        .shadow(smallvec::smallvec![gpui::BoxShadow {
461                            color: gpui::black().opacity(0.15),
462                            offset: point(px(1.), px(-1.)),
463                            blur_radius: px(3.),
464                            spread_radius: px(0.),
465                        }])
466                        .child(
467                            h_flex()
468                                .id("edits-container")
469                                .cursor_pointer()
470                                .p_1p5()
471                                .justify_between()
472                                .when(is_edit_changes_expanded, |this| {
473                                    this.border_b_1().border_color(border_color)
474                                })
475                                .on_click(cx.listener(|this, _, window, cx| {
476                                    this.handle_review_click(window, cx)
477                                }))
478                                .child(
479                                    h_flex()
480                                        .gap_1()
481                                        .child(
482                                            Disclosure::new(
483                                                "edits-disclosure",
484                                                is_edit_changes_expanded,
485                                            )
486                                            .on_click(
487                                                cx.listener(|this, _ev, _window, cx| {
488                                                    this.edits_expanded = !this.edits_expanded;
489                                                    cx.notify();
490                                                }),
491                                            ),
492                                        )
493                                        .child(
494                                            Label::new("Edits")
495                                                .size(LabelSize::Small)
496                                                .color(Color::Muted),
497                                        )
498                                        .child(
499                                            Label::new("")
500                                                .size(LabelSize::XSmall)
501                                                .color(Color::Muted),
502                                        )
503                                        .child(
504                                            Label::new(format!(
505                                                "{} {}",
506                                                changed_buffers_count,
507                                                if changed_buffers_count == 1 {
508                                                    "file"
509                                                } else {
510                                                    "files"
511                                                }
512                                            ))
513                                            .size(LabelSize::Small)
514                                            .color(Color::Muted),
515                                        ),
516                                )
517                                .child(
518                                    Button::new("review", "Review Changes")
519                                        .label_size(LabelSize::Small)
520                                        .key_binding(
521                                            KeyBinding::for_action_in(
522                                                &OpenAgentDiff,
523                                                &focus_handle,
524                                                window,
525                                                cx,
526                                            )
527                                            .map(|kb| kb.size(rems_from_px(12.))),
528                                        )
529                                        .on_click(cx.listener(|this, _, window, cx| {
530                                            this.handle_review_click(window, cx)
531                                        })),
532                                ),
533                        )
534                        .when(is_edit_changes_expanded, |parent| {
535                            parent.child(
536                                v_flex().children(
537                                    changed_buffers.into_iter().enumerate().flat_map(
538                                        |(index, (buffer, _diff))| {
539                                            let file = buffer.read(cx).file()?;
540                                            let path = file.path();
541
542                                            let parent_label = path.parent().and_then(|parent| {
543                                                let parent_str = parent.to_string_lossy();
544
545                                                if parent_str.is_empty() {
546                                                    None
547                                                } else {
548                                                    Some(
549                                                        Label::new(format!(
550                                                            "/{}{}",
551                                                            parent_str,
552                                                            std::path::MAIN_SEPARATOR_STR
553                                                        ))
554                                                        .color(Color::Muted)
555                                                        .size(LabelSize::XSmall)
556                                                        .buffer_font(cx),
557                                                    )
558                                                }
559                                            });
560
561                                            let name_label = path.file_name().map(|name| {
562                                                Label::new(name.to_string_lossy().to_string())
563                                                    .size(LabelSize::XSmall)
564                                                    .buffer_font(cx)
565                                            });
566
567                                            let file_icon = FileIcons::get_icon(&path, cx)
568                                                .map(Icon::from_path)
569                                                .map(|icon| {
570                                                    icon.color(Color::Muted).size(IconSize::Small)
571                                                })
572                                                .unwrap_or_else(|| {
573                                                    Icon::new(IconName::File)
574                                                        .color(Color::Muted)
575                                                        .size(IconSize::Small)
576                                                });
577
578                                            let hover_color = cx.theme()
579                                                .colors()
580                                                .element_background
581                                                .blend(cx.theme().colors().editor_foreground.opacity(0.025));
582
583                                            let overlay_gradient = linear_gradient(
584                                                90.,
585                                                linear_color_stop(
586                                                    editor_bg_color,
587                                                    1.,
588                                                ),
589                                                linear_color_stop(
590                                                    editor_bg_color
591                                                        .opacity(0.2),
592                                                    0.,
593                                                ),
594                                            );
595
596                                            let overlay_gradient_hover = linear_gradient(
597                                                90.,
598                                                linear_color_stop(
599                                                    hover_color,
600                                                    1.,
601                                                ),
602                                                linear_color_stop(
603                                                    hover_color
604                                                        .opacity(0.2),
605                                                    0.,
606                                                ),
607                                            );
608
609                                            let element = h_flex()
610                                                .group("edited-code")
611                                                .id(("file-container", index))
612                                                .cursor_pointer()
613                                                .relative()
614                                                .py_1()
615                                                .pl_2()
616                                                .pr_1()
617                                                .gap_2()
618                                                .justify_between()
619                                                .bg(cx.theme().colors().editor_background)
620                                                .hover(|style| style.bg(hover_color))
621                                                .when(index + 1 < changed_buffers_count, |parent| {
622                                                    parent.border_color(border_color).border_b_1()
623                                                })
624                                                .child(
625                                                    h_flex()
626                                                        .id("file-name")
627                                                        .pr_8()
628                                                        .gap_1p5()
629                                                        .max_w_full()
630                                                        .overflow_x_scroll()
631                                                        .child(file_icon)
632                                                        .child(
633                                                            h_flex()
634                                                                .gap_0p5()
635                                                                .children(name_label)
636                                                                .children(parent_label)
637                                                        ) // TODO: show lines changed
638                                                        .child(
639                                                            Label::new("+")
640                                                                .color(Color::Created),
641                                                        )
642                                                        .child(
643                                                            Label::new("-")
644                                                                .color(Color::Deleted),
645                                                        ),
646                                                )
647                                                .child(
648                                                    div().visible_on_hover("edited-code").child(
649                                                        Button::new("review", "Review")
650                                                            .label_size(LabelSize::Small)
651                                                            .on_click({
652                                                                let buffer = buffer.clone();
653                                                                cx.listener(move |this, _, window, cx| {
654                                                                    this.handle_file_click(buffer.clone(), window, cx);
655                                                                })
656                                                            })
657                                                    )
658                                                )
659                                                .child(
660                                                    div()
661                                                        .id("gradient-overlay")
662                                                        .absolute()
663                                                        .h_5_6()
664                                                        .w_12()
665                                                        .bottom_0()
666                                                        .right(px(52.))
667                                                        .bg(overlay_gradient)
668                                                        .group_hover("edited-code", |style| style.bg(overlay_gradient_hover))
669                                                    ,
670                                                )
671                                                .on_click({
672                                                    let buffer = buffer.clone();
673                                                    cx.listener(move |this, _, window, cx| {
674                                                        this.handle_file_click(buffer.clone(), window, cx);
675                                                    })
676                                                });
677
678                                            Some(element)
679                                        },
680                                    ),
681                                ),
682                            )
683                        }),
684                )
685            })
686            .child(
687                v_flex()
688                    .key_context("MessageEditor")
689                    .on_action(cx.listener(Self::chat))
690                    .on_action(cx.listener(|this, _: &ToggleProfileSelector, window, cx| {
691                        this.profile_selector
692                            .read(cx)
693                            .menu_handle()
694                            .toggle(window, cx);
695                    }))
696                    .on_action(cx.listener(|this, _: &ToggleModelSelector, window, cx| {
697                        this.model_selector
698                            .update(cx, |model_selector, cx| model_selector.toggle(window, cx));
699                    }))
700                    .on_action(cx.listener(Self::toggle_context_picker))
701                    .on_action(cx.listener(Self::remove_all_context))
702                    .on_action(cx.listener(Self::move_up))
703                    .on_action(cx.listener(Self::toggle_chat_mode))
704                    .on_action(cx.listener(Self::expand_message_editor))
705                    .gap_2()
706                    .p_2()
707                    .bg(editor_bg_color)
708                    .border_t_1()
709                    .border_color(cx.theme().colors().border)
710                    .child(
711                        h_flex()
712                            .items_start()
713                            .justify_between()
714                            .child(self.context_strip.clone())
715                            .child(
716                                IconButton::new("toggle-height", expand_icon)
717                                    .icon_size(IconSize::XSmall)
718                                    .icon_color(Color::Muted)
719                                    .tooltip(move |window, cx| {
720                                        let focus_handle = focus_handle.clone();
721                                        let expand_label = if is_editor_expanded {
722                                            "Minimize Message Editor".to_string()
723                                        } else {
724                                            "Expand Message Editor".to_string()
725                                        };
726
727                                        Tooltip::for_action_in(
728                                            expand_label,
729                                            &ExpandMessageEditor,
730                                            &focus_handle,
731                                            window,
732                                            cx,
733                                        )
734                                    })
735                                    .on_click(cx.listener(|_, _, window, cx| {
736                                        window.dispatch_action(Box::new(ExpandMessageEditor), cx);
737                                    }))
738                            )
739                    )
740                    .child(
741                        v_flex()
742                            .size_full()
743                            .gap_4()
744                            .when(is_editor_expanded, |this| this.h(vh(0.8, window)).justify_between())
745                            .child(div().when(is_editor_expanded, |this| this.h_full()).child({
746                                    let settings = ThemeSettings::get_global(cx);
747
748                                    let text_style = TextStyle {
749                                        color: cx.theme().colors().text,
750                                        font_family: settings.buffer_font.family.clone(),
751                                        font_fallbacks: settings.buffer_font.fallbacks.clone(),
752                                        font_features: settings.buffer_font.features.clone(),
753                                        font_size: font_size.into(),
754                                        line_height: line_height.into(),
755                                        ..Default::default()
756                                    };
757
758                                    EditorElement::new(
759                                        &self.editor,
760                                        EditorStyle {
761                                            background: editor_bg_color,
762                                            local_player: cx.theme().players().local(),
763                                            text: text_style,
764                                            syntax: cx.theme().syntax().clone(),
765                                            ..Default::default()
766                                        },
767                                    ).into_any()
768                            }))
769                            .child(
770                                PopoverMenu::new("inline-context-picker")
771                                    .menu(move |window, cx| {
772                                        inline_context_picker.update(cx, |this, cx| {
773                                            this.init(window, cx);
774                                        });
775                                        Some(inline_context_picker.clone())
776                                    })
777                                    .attach(gpui::Corner::TopLeft)
778                                    .anchor(gpui::Corner::BottomLeft)
779                                    .offset(gpui::Point {
780                                        x: px(0.0),
781                                        y: (-ThemeSettings::get_global(cx).ui_font_size(cx) * 2)
782                                            - px(4.0),
783                                    })
784                                    .with_handle(self.inline_context_picker_menu_handle.clone()),
785                            )
786                            .child(
787                                h_flex()
788                                    .flex_none()
789                                    .justify_between()
790                                    .child(h_flex().gap_2().child(self.profile_selector.clone()))
791                                    .child(
792                                        h_flex().gap_1()
793                                            .child(self.model_selector.clone())
794                                            .map(move |parent| {
795                                                if is_generating {
796                                                    parent.child(
797                                                        IconButton::new("stop-generation", IconName::StopFilled)
798                                                            .icon_color(Color::Error)
799                                                            .style(ButtonStyle::Tinted(ui::TintColor::Error))
800                                                            .tooltip(move |window, cx| {
801                                                                Tooltip::for_action(
802                                                                    "Stop Generation",
803                                                                    &editor::actions::Cancel,
804                                                                    window,
805                                                                    cx,
806                                                                )
807                                                            })
808                                                            .on_click({
809                                                                let focus_handle = focus_handle_clone.clone();
810                                                                move |_event, window, cx| {
811                                                                    focus_handle.dispatch_action(
812                                                                        &editor::actions::Cancel,
813                                                                        window,
814                                                                        cx,
815                                                                    );
816                                                                }
817                                                            })
818                                                            .with_animation(
819                                                                "pulsating-label",
820                                                                Animation::new(Duration::from_secs(2))
821                                                                    .repeat()
822                                                                    .with_easing(pulsating_between(0.4, 1.0)),
823                                                                |icon_button, delta| icon_button.alpha(delta),
824                                                            ),
825                                                    )
826                                                } else {
827                                                    parent.child(
828                                                        IconButton::new("send-message", IconName::Send)
829                                                            .icon_color(Color::Accent)
830                                                            .style(ButtonStyle::Filled)
831                                                            .disabled(
832                                                                is_editor_empty
833                                                                    || !is_model_selected
834                                                                    || self.waiting_for_summaries_to_send
835                                                            )
836                                                            .on_click({
837                                                                let focus_handle = focus_handle_clone.clone();
838                                                                move |_event, window, cx| {
839                                                                    focus_handle.dispatch_action(&Chat, window, cx);
840                                                                }
841                                                            })
842                                                            .when(!is_editor_empty && is_model_selected, |button| {
843                                                                button.tooltip(move |window, cx| {
844                                                                    Tooltip::for_action(
845                                                                        "Send",
846                                                                        &Chat,
847                                                                        window,
848                                                                        cx,
849                                                                    )
850                                                                })
851                                                            })
852                                                            .when(is_editor_empty, |button| {
853                                                                button.tooltip(Tooltip::text(
854                                                                    "Type a message to submit",
855                                                                ))
856                                                            })
857                                                            .when(!is_model_selected, |button| {
858                                                                button.tooltip(Tooltip::text(
859                                                                    "Select a model to continue",
860                                                                ))
861                                                            })
862                                                    )
863                                                }
864                                            })
865                                    ),
866                            ),
867                    )
868            )
869            .when(total_token_usage.ratio != TokenUsageRatio::Normal, |parent| {
870                parent.child(
871                    h_flex()
872                        .p_2()
873                        .gap_2()
874                        .flex_wrap()
875                        .justify_between()
876                        .bg(cx.theme().status().warning_background.opacity(0.1))
877                        .border_t_1()
878                        .border_color(cx.theme().colors().border)
879                        .child(
880                            h_flex()
881                                .gap_2()
882                                .items_start()
883                                .child(
884                                    h_flex()
885                                        .h(line_height)
886                                        .justify_center()
887                                        .child(
888                                            Icon::new(IconName::Warning)
889                                                .color(Color::Warning)
890                                                .size(IconSize::XSmall),
891                                        ),
892                                )
893                                .child(
894                                    v_flex()
895                                        .mr_auto()
896                                        .child(Label::new("Thread reaching the token limit soon").size(LabelSize::Small))
897                                        .child(
898                                            Label::new(
899                                                "Start a new thread from a summary to continue the conversation.",
900                                            )
901                                            .size(LabelSize::Small)
902                                            .color(Color::Muted),
903                                        ),
904                                ),
905                        )
906                        .child(
907                            Button::new("new-thread", "Start New Thread")
908                                .on_click(cx.listener(|this, _, window, cx| {
909                                    let from_thread_id = Some(this.thread.read(cx).id().clone());
910
911                                    window.dispatch_action(Box::new(NewThread {
912                                        from_thread_id
913                                    }), cx);
914                                }))
915                                .icon(IconName::Plus)
916                                .icon_position(IconPosition::Start)
917                                .icon_size(IconSize::Small)
918                                .style(ButtonStyle::Tinted(ui::TintColor::Accent))
919                                .label_size(LabelSize::Small),
920                        ),
921                )
922            })
923    }
924}