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