1use crate::{ui::ProjectIndexButton, AssistantChat, CompletionProvider};
2use editor::{Editor, EditorElement, EditorStyle};
3use gpui::{AnyElement, FontStyle, FontWeight, TextStyle, View, WeakView, WhiteSpace};
4use settings::Settings;
5use theme::ThemeSettings;
6use ui::{popover_menu, prelude::*, ButtonLike, ContextMenu, Tooltip};
7
8#[derive(IntoElement)]
9pub struct Composer {
10 editor: View<Editor>,
11 project_index_button: Option<View<ProjectIndexButton>>,
12 model_selector: AnyElement,
13}
14
15impl Composer {
16 pub fn new(
17 editor: View<Editor>,
18 project_index_button: Option<View<ProjectIndexButton>>,
19 model_selector: AnyElement,
20 ) -> Self {
21 Self {
22 editor,
23 project_index_button,
24 model_selector,
25 }
26 }
27
28 fn render_tools(&mut self, _cx: &mut WindowContext) -> impl IntoElement {
29 h_flex().children(
30 self.project_index_button
31 .clone()
32 .map(|view| view.into_any_element()),
33 )
34 }
35}
36
37impl RenderOnce for Composer {
38 fn render(mut self, cx: &mut WindowContext) -> impl IntoElement {
39 let font_size = rems(0.875);
40 let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
41
42 h_flex().w_full().items_start().mt_2().child(
43 v_flex().size_full().gap_1().child(
44 v_flex()
45 .w_full()
46 .p_3()
47 .bg(cx.theme().colors().editor_background)
48 .rounded_lg()
49 .child(
50 v_flex()
51 .justify_between()
52 .w_full()
53 .gap_2()
54 .child({
55 let settings = ThemeSettings::get_global(cx);
56 let text_style = TextStyle {
57 color: cx.theme().colors().editor_foreground,
58 font_family: settings.buffer_font.family.clone(),
59 font_features: settings.buffer_font.features.clone(),
60 font_size: font_size.into(),
61 font_weight: FontWeight::NORMAL,
62 font_style: FontStyle::Normal,
63 line_height: line_height.into(),
64 background_color: None,
65 underline: None,
66 strikethrough: None,
67 white_space: WhiteSpace::Normal,
68 };
69
70 EditorElement::new(
71 &self.editor,
72 EditorStyle {
73 background: cx.theme().colors().editor_background,
74 local_player: cx.theme().players().local(),
75 text: text_style,
76 ..Default::default()
77 },
78 )
79 })
80 .child(
81 h_flex()
82 .flex_none()
83 .gap_2()
84 .justify_between()
85 .w_full()
86 .child(h_flex().gap_1().child(self.render_tools(cx)))
87 .child(h_flex().gap_1().child(self.model_selector)),
88 ),
89 ),
90 ),
91 )
92 }
93}
94
95#[derive(IntoElement)]
96pub struct ModelSelector {
97 assistant_chat: WeakView<AssistantChat>,
98 model: String,
99}
100
101impl ModelSelector {
102 pub fn new(assistant_chat: WeakView<AssistantChat>, model: String) -> Self {
103 Self {
104 assistant_chat,
105 model,
106 }
107 }
108}
109
110impl RenderOnce for ModelSelector {
111 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
112 popover_menu("model-switcher")
113 .menu(move |cx| {
114 ContextMenu::build(cx, |mut menu, cx| {
115 for model in CompletionProvider::get(cx).available_models() {
116 menu = menu.custom_entry(
117 {
118 let model = model.clone();
119 move |_| Label::new(model.clone()).into_any_element()
120 },
121 {
122 let assistant_chat = self.assistant_chat.clone();
123 move |cx| {
124 _ = assistant_chat.update(cx, |assistant_chat, cx| {
125 assistant_chat.model = model.clone();
126 cx.notify();
127 });
128 }
129 },
130 );
131 }
132 menu
133 })
134 .into()
135 })
136 .trigger(
137 ButtonLike::new("active-model")
138 .child(
139 h_flex()
140 .w_full()
141 .gap_0p5()
142 .child(
143 div()
144 .overflow_x_hidden()
145 .flex_grow()
146 .whitespace_nowrap()
147 .child(
148 Label::new(self.model)
149 .size(LabelSize::Small)
150 .color(Color::Muted),
151 ),
152 )
153 .child(
154 div().child(
155 Icon::new(IconName::ChevronDown)
156 .color(Color::Muted)
157 .size(IconSize::XSmall),
158 ),
159 ),
160 )
161 .style(ButtonStyle::Subtle)
162 .tooltip(move |cx| Tooltip::text("Change Model", cx)),
163 )
164 .anchor(gpui::AnchorCorner::BottomRight)
165 }
166}