1use agent_settings::AgentSettings;
  2use anyhow::Result;
  3use cloud_llm_client::CompletionIntent;
  4use command_palette::humanize_action_name;
  5use futures::StreamExt as _;
  6use gpui::{
  7    Action, AppContext as _, DismissEvent, Entity, EventEmitter, Focusable, IntoElement, Task,
  8    WeakEntity,
  9};
 10use language_model::{
 11    ConfiguredModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
 12};
 13use picker::{Picker, PickerDelegate};
 14use settings::Settings as _;
 15use ui::{
 16    App, Context, InteractiveElement, ListItem, ParentElement as _, Render, Styled as _, Window,
 17    div, rems,
 18};
 19use util::ResultExt;
 20use workspace::{ModalView, Workspace};
 21
 22pub fn init(cx: &mut App) {
 23    cx.observe_new(MagicPalette::register).detach();
 24}
 25
 26gpui::actions!(magic_palette, [Toggle]);
 27
 28struct MagicPalette {
 29    picker: Entity<Picker<MagicPaletteDelegate>>,
 30    matches: Vec<Command>,
 31}
 32
 33impl ModalView for MagicPalette {}
 34
 35impl EventEmitter<DismissEvent> for MagicPalette {}
 36
 37impl Focusable for MagicPalette {
 38    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
 39        self.picker.focus_handle(cx)
 40    }
 41}
 42
 43impl MagicPalette {
 44    fn register(
 45        workspace: &mut Workspace,
 46        _window: Option<&mut Window>,
 47        _cx: &mut Context<Workspace>,
 48    ) {
 49        workspace.register_action(|workspace, _: &Toggle, window, cx| {
 50            Self::toggle(workspace, window, cx)
 51        });
 52    }
 53
 54    fn toggle(workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>) {
 55        if agent_settings::AgentSettings::get_global(cx).enabled(cx) {
 56            workspace.toggle_modal(window, cx, |window, cx| MagicPalette::new(window, cx));
 57        }
 58    }
 59
 60    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
 61        let this = cx.weak_entity();
 62        let delegate = MagicPaletteDelegate::new(this);
 63        let picker = cx.new(|cx| {
 64            let picker = Picker::uniform_list(delegate, window, cx);
 65            picker
 66        });
 67        Self {
 68            picker,
 69            matches: vec![],
 70        }
 71    }
 72}
 73
 74impl Render for MagicPalette {
 75    fn render(&mut self, _window: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
 76        div()
 77            .key_context("MagicPalette")
 78            .w(rems(34.))
 79            .child(self.picker.clone())
 80    }
 81}
 82
 83#[derive(Debug)]
 84struct Command {
 85    name: String,
 86    action: Box<dyn Action>,
 87}
 88
 89enum MagicPaletteMode {
 90    WriteQuery,
 91    SelectResult(Vec<Command>),
 92}
 93
 94struct MagicPaletteDelegate {
 95    query: String,
 96    llm_generation_task: Task<Result<()>>,
 97    magic_palette: WeakEntity<MagicPalette>,
 98    mode: MagicPaletteMode,
 99    selected_index: usize,
100}
101
102impl MagicPaletteDelegate {
103    fn new(magic_palette: WeakEntity<MagicPalette>) -> Self {
104        Self {
105            query: String::new(),
106            llm_generation_task: Task::ready(Ok(())),
107            magic_palette,
108            mode: MagicPaletteMode::WriteQuery,
109            selected_index: 0,
110        }
111    }
112}
113
114impl PickerDelegate for MagicPaletteDelegate {
115    type ListItem = ListItem;
116
117    fn match_count(&self) -> usize {
118        match &self.mode {
119            MagicPaletteMode::WriteQuery => 0,
120            MagicPaletteMode::SelectResult(commands) => commands.len(),
121        }
122    }
123
124    fn selected_index(&self) -> usize {
125        self.selected_index
126    }
127
128    fn set_selected_index(
129        &mut self,
130        ix: usize,
131        _window: &mut Window,
132        _cx: &mut Context<picker::Picker<Self>>,
133    ) {
134        self.selected_index = ix;
135    }
136
137    fn placeholder_text(&self, _window: &mut Window, _cx: &mut ui::App) -> std::sync::Arc<str> {
138        "Ask Zed AI what actions you want to perform...".into()
139    }
140
141    fn update_matches(
142        &mut self,
143        query: String,
144        _window: &mut Window,
145        _cx: &mut Context<picker::Picker<Self>>,
146    ) -> gpui::Task<()> {
147        self.query = query;
148        Task::ready(())
149    }
150
151    fn confirm(
152        &mut self,
153        _secondary: bool,
154        window: &mut Window,
155        cx: &mut Context<picker::Picker<Self>>,
156    ) {
157        match &self.mode {
158            MagicPaletteMode::WriteQuery => {
159                let Some(ConfiguredModel { provider, model }) =
160                    LanguageModelRegistry::read_global(cx).commit_message_model()
161                else {
162                    return;
163                };
164                let temperature = AgentSettings::temperature_for_model(&model, cx);
165                let query = self.query.clone();
166                let actions = window.available_actions(cx);
167                self.llm_generation_task = cx.spawn_in(window, async move |this, cx| {
168                    if let Some(task) = cx.update(|_, cx| {
169                        if !provider.is_authenticated(cx) {
170                            Some(provider.authenticate(cx))
171                        } else {
172                            None
173                        }
174                    })? {
175                        task.await.log_err();
176                    };
177
178                    let actions = actions
179                        .into_iter()
180                        .map(|actions| actions.name())
181                        .collect::<Vec<&'static str>>();
182                    let actions = actions.join("\n");
183                    let prompt = format!(
184                        "You are helping a user find the most relevant actions in Zed editor based on their natural language query.
185
186User query: \"{query}\"
187
188Available actions in Zed:
189{actions}
190
191Instructions:
1921. Analyze the user's query to understand their intent
1932. Match the query against the available actions, considering:
194   - Exact keyword matches
195   - Semantic similarity (e.g., \"open file\" matches \"workspace::Open\")
196   - Common synonyms and alternative phrasings
197   - Partial matches where relevant
1983. Return the top 5-10 most relevant actions in order of relevance
1994. Return each action name exactly as shown in the list above
2005. If no good matches exist, return the closest alternatives
201
202Format your response as a simple list of action names, one per line, with no additional text or explanation."
203                    );
204                    dbg!(&prompt);
205
206                    let request = LanguageModelRequest {
207                        thread_id: None,
208                        prompt_id: None,
209                        intent: Some(CompletionIntent::GenerateGitCommitMessage),
210                        mode: None,
211                        messages: vec![LanguageModelRequestMessage {
212                            role: Role::User,
213                            content: vec![prompt.into()],
214                            cache: false,
215                        }],
216                        tools: Vec::new(),
217                        tool_choice: None,
218                        stop: Vec::new(),
219                        temperature,
220                        thinking_allowed: false,
221                    };
222
223                    let stream = model.stream_completion_text(request, cx);
224                    dbg!("pinging stream");
225                    let mut messages = stream.await?;
226                    let mut buffer = String::new();
227                    while let Some(Ok(message)) = messages.stream.next().await {
228                        buffer.push_str(&message);
229                    }
230
231                    dbg!(&buffer);
232
233                    // Split result by `\n` and for each string, call `cx.build_action`.
234                    let commands = cx.update(move |_window, cx| {
235                        let mut commands: Vec<Command> = vec![];
236
237                        for name in buffer.lines() {
238                            dbg!(name);
239
240                            let action = cx.build_action(name, None);
241                            match action {
242                                Ok(action) => {
243                                    commands.push(Command { action: action, name: humanize_action_name(name) })
244                                    },
245                                Err(err) => {
246                                    log::error!("Failed to build action: {}", err);
247                                }
248                            }
249                        }
250
251                        commands
252                    });
253
254                    dbg!(&commands);
255                    if let Ok(commands) = commands {
256                        let _ = this.update(cx, |this, cx| {
257                            let _ = this.delegate.magic_palette.update(cx, |magic_palette, _| {
258                                magic_palette.matches = commands;
259                            });
260                        });
261                    }
262
263                    //
264                    Ok(())
265                });
266            }
267            MagicPaletteMode::SelectResult(commands) => todo!(),
268        }
269    }
270
271    fn dismissed(&mut self, _: &mut Window, cx: &mut Context<picker::Picker<Self>>) {
272        self.magic_palette
273            .update(cx, |_, cx| {
274                cx.emit(DismissEvent);
275            })
276            .ok();
277    }
278
279    fn render_match(
280        &self,
281        ix: usize,
282        selected: bool,
283        window: &mut Window,
284        cx: &mut Context<picker::Picker<Self>>,
285    ) -> Option<Self::ListItem> {
286        None
287    }
288
289    fn confirm_input(
290        &mut self,
291        _secondary: bool,
292        _window: &mut Window,
293        _: &mut Context<picker::Picker<Self>>,
294    ) {
295    }
296}