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