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