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