message_editor.rs

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