message_editor.rs

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