1use std::sync::Arc;
2
3use assistant_settings::AssistantSettings;
4use assistant_tool::ToolWorkingSet;
5use fs::Fs;
6use gpui::{prelude::*, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable};
7use settings::Settings as _;
8use ui::{prelude::*, ListItem, ListItemSpacing, Navigable, NavigableEntry};
9use workspace::{ModalView, Workspace};
10
11use crate::assistant_configuration::profile_picker::{ProfilePicker, ProfilePickerDelegate};
12use crate::assistant_configuration::tool_picker::{ToolPicker, ToolPickerDelegate};
13use crate::{AssistantPanel, ManageProfiles};
14
15enum Mode {
16 ChooseProfile(Entity<ProfilePicker>),
17 ViewProfile(ViewProfileMode),
18 ConfigureTools(Entity<ToolPicker>),
19}
20
21#[derive(Clone)]
22pub struct ViewProfileMode {
23 profile_id: Arc<str>,
24 configure_tools: NavigableEntry,
25}
26
27pub struct ManageProfilesModal {
28 fs: Arc<dyn Fs>,
29 tools: Arc<ToolWorkingSet>,
30 focus_handle: FocusHandle,
31 mode: Mode,
32}
33
34impl ManageProfilesModal {
35 pub fn register(
36 workspace: &mut Workspace,
37 _window: Option<&mut Window>,
38 _cx: &mut Context<Workspace>,
39 ) {
40 workspace.register_action(|workspace, _: &ManageProfiles, window, cx| {
41 if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
42 let fs = workspace.app_state().fs.clone();
43 let thread_store = panel.read(cx).thread_store().read(cx);
44 let tools = thread_store.tools();
45 workspace.toggle_modal(window, cx, |window, cx| Self::new(fs, tools, window, cx))
46 }
47 });
48 }
49
50 pub fn new(
51 fs: Arc<dyn Fs>,
52 tools: Arc<ToolWorkingSet>,
53 window: &mut Window,
54 cx: &mut Context<Self>,
55 ) -> Self {
56 let focus_handle = cx.focus_handle();
57 let handle = cx.entity();
58
59 Self {
60 fs,
61 tools,
62 focus_handle,
63 mode: Mode::ChooseProfile(cx.new(|cx| {
64 let delegate = ProfilePickerDelegate::new(
65 move |profile_id, window, cx| {
66 handle.update(cx, |this, cx| {
67 this.view_profile(profile_id.clone(), window, cx);
68 })
69 },
70 cx,
71 );
72 ProfilePicker::new(delegate, window, cx)
73 })),
74 }
75 }
76
77 pub fn view_profile(
78 &mut self,
79 profile_id: Arc<str>,
80 window: &mut Window,
81 cx: &mut Context<Self>,
82 ) {
83 self.mode = Mode::ViewProfile(ViewProfileMode {
84 profile_id,
85 configure_tools: NavigableEntry::focusable(cx),
86 });
87 self.focus_handle(cx).focus(window);
88 }
89
90 fn configure_tools(
91 &mut self,
92 profile_id: Arc<str>,
93 window: &mut Window,
94 cx: &mut Context<Self>,
95 ) {
96 let settings = AssistantSettings::get_global(cx);
97 let Some(profile) = settings.profiles.get(&profile_id).cloned() else {
98 return;
99 };
100
101 self.mode = Mode::ConfigureTools(cx.new(|cx| {
102 let delegate = ToolPickerDelegate::new(
103 self.fs.clone(),
104 self.tools.clone(),
105 profile_id,
106 profile,
107 cx,
108 );
109 ToolPicker::new(delegate, window, cx)
110 }));
111 self.focus_handle(cx).focus(window);
112 }
113
114 fn confirm(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
115
116 fn cancel(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
117}
118
119impl ModalView for ManageProfilesModal {}
120
121impl Focusable for ManageProfilesModal {
122 fn focus_handle(&self, cx: &App) -> FocusHandle {
123 match &self.mode {
124 Mode::ChooseProfile(profile_picker) => profile_picker.focus_handle(cx),
125 Mode::ConfigureTools(tool_picker) => tool_picker.focus_handle(cx),
126 Mode::ViewProfile(_) => self.focus_handle.clone(),
127 }
128 }
129}
130
131impl EventEmitter<DismissEvent> for ManageProfilesModal {}
132
133impl ManageProfilesModal {
134 fn render_view_profile(
135 &mut self,
136 mode: ViewProfileMode,
137 window: &mut Window,
138 cx: &mut Context<Self>,
139 ) -> impl IntoElement {
140 Navigable::new(
141 div()
142 .track_focus(&self.focus_handle(cx))
143 .size_full()
144 .child(
145 v_flex().child(
146 div()
147 .id("configure-tools")
148 .track_focus(&mode.configure_tools.focus_handle)
149 .on_action({
150 let profile_id = mode.profile_id.clone();
151 cx.listener(move |this, _: &menu::Confirm, window, cx| {
152 this.configure_tools(profile_id.clone(), window, cx);
153 })
154 })
155 .child(
156 ListItem::new("configure-tools")
157 .toggle_state(
158 mode.configure_tools
159 .focus_handle
160 .contains_focused(window, cx),
161 )
162 .inset(true)
163 .spacing(ListItemSpacing::Sparse)
164 .start_slot(Icon::new(IconName::Cog))
165 .child(Label::new("Configure Tools"))
166 .on_click({
167 let profile_id = mode.profile_id.clone();
168 cx.listener(move |this, _, window, cx| {
169 this.configure_tools(profile_id.clone(), window, cx);
170 })
171 }),
172 ),
173 ),
174 )
175 .into_any_element(),
176 )
177 .entry(mode.configure_tools)
178 }
179}
180
181impl Render for ManageProfilesModal {
182 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
183 div()
184 .elevation_3(cx)
185 .w(rems(34.))
186 .key_context("ManageProfilesModal")
187 .on_action(cx.listener(|this, _: &menu::Cancel, window, cx| this.cancel(window, cx)))
188 .on_action(cx.listener(|this, _: &menu::Confirm, window, cx| this.confirm(window, cx)))
189 .capture_any_mouse_down(cx.listener(|this, _, window, cx| {
190 this.focus_handle(cx).focus(window);
191 }))
192 .on_mouse_down_out(cx.listener(|_this, _, _, cx| cx.emit(DismissEvent)))
193 .child(match &self.mode {
194 Mode::ChooseProfile(profile_picker) => profile_picker.clone().into_any_element(),
195 Mode::ViewProfile(mode) => self
196 .render_view_profile(mode.clone(), window, cx)
197 .into_any_element(),
198 Mode::ConfigureTools(tool_picker) => tool_picker.clone().into_any_element(),
199 })
200 }
201}