assistant_panel.rs

   1use crate::Assistant;
   2use crate::assistant_configuration::{ConfigurationView, ConfigurationViewEvent};
   3use crate::{
   4    DeployHistory, InlineAssistant, NewChat, terminal_inline_assistant::TerminalInlineAssistant,
   5};
   6use anyhow::{Result, anyhow};
   7use assistant_context_editor::{
   8    AssistantContext, AssistantPanelDelegate, ContextEditor, ContextEditorToolbarItem,
   9    ContextEditorToolbarItemEvent, ContextHistory, ContextId, ContextStore, ContextStoreEvent,
  10    DEFAULT_TAB_TITLE, InsertDraggedFiles, SlashCommandCompletionProvider,
  11    make_lsp_adapter_delegate,
  12};
  13use assistant_settings::{AssistantDockPosition, AssistantSettings};
  14use assistant_slash_command::SlashCommandWorkingSet;
  15use client::{Client, Status, proto};
  16use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
  17use fs::Fs;
  18use gpui::{
  19    Action, App, AsyncWindowContext, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable,
  20    InteractiveElement, IntoElement, ParentElement, Pixels, Render, Styled, Subscription, Task,
  21    UpdateGlobal, WeakEntity, prelude::*,
  22};
  23use language::LanguageRegistry;
  24use language_model::{
  25    AuthenticateError, ConfiguredModel, LanguageModelProviderId, LanguageModelRegistry,
  26    ZED_CLOUD_PROVIDER_ID,
  27};
  28use project::Project;
  29use prompt_library::{PromptLibrary, open_prompt_library};
  30use prompt_store::PromptBuilder;
  31
  32use search::{BufferSearchBar, buffer_search::DivRegistrar};
  33use settings::{Settings, update_settings_file};
  34use smol::stream::StreamExt;
  35
  36use std::ops::Range;
  37use std::{ops::ControlFlow, path::PathBuf, sync::Arc};
  38use terminal_view::{TerminalView, terminal_panel::TerminalPanel};
  39use ui::{ContextMenu, PopoverMenu, Tooltip, prelude::*};
  40use util::{ResultExt, maybe};
  41use workspace::DraggedTab;
  42use workspace::{
  43    DraggedSelection, Pane, ToggleZoom, Workspace,
  44    dock::{DockPosition, Panel, PanelEvent},
  45    pane,
  46};
  47use zed_actions::assistant::{InlineAssist, OpenPromptLibrary, ShowConfiguration, ToggleFocus};
  48
  49pub fn init(cx: &mut App) {
  50    workspace::FollowableViewRegistry::register::<ContextEditor>(cx);
  51    cx.observe_new(
  52        |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
  53            workspace
  54                .register_action(ContextEditor::quote_selection)
  55                .register_action(ContextEditor::insert_selection)
  56                .register_action(ContextEditor::copy_code)
  57                .register_action(ContextEditor::insert_dragged_files)
  58                .register_action(AssistantPanel::show_configuration)
  59                .register_action(AssistantPanel::create_new_context)
  60                .register_action(AssistantPanel::restart_context_servers)
  61                .register_action(|workspace, _: &OpenPromptLibrary, window, cx| {
  62                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
  63                        workspace.focus_panel::<AssistantPanel>(window, cx);
  64                        panel.update(cx, |panel, cx| {
  65                            panel.deploy_prompt_library(&OpenPromptLibrary, window, cx)
  66                        });
  67                    }
  68                });
  69        },
  70    )
  71    .detach();
  72
  73    cx.observe_new(
  74        |terminal_panel: &mut TerminalPanel, _, cx: &mut Context<TerminalPanel>| {
  75            terminal_panel.set_assistant_enabled(Assistant::enabled(cx), cx);
  76        },
  77    )
  78    .detach();
  79}
  80
  81pub enum AssistantPanelEvent {
  82    ContextEdited,
  83}
  84
  85pub struct AssistantPanel {
  86    pane: Entity<Pane>,
  87    workspace: WeakEntity<Workspace>,
  88    width: Option<Pixels>,
  89    height: Option<Pixels>,
  90    project: Entity<Project>,
  91    context_store: Entity<ContextStore>,
  92    languages: Arc<LanguageRegistry>,
  93    fs: Arc<dyn Fs>,
  94    subscriptions: Vec<Subscription>,
  95    model_summary_editor: Entity<Editor>,
  96    authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>,
  97    configuration_subscription: Option<Subscription>,
  98    client_status: Option<client::Status>,
  99    watch_client_status: Option<Task<()>>,
 100    pub(crate) show_zed_ai_notice: bool,
 101}
 102
 103enum InlineAssistTarget {
 104    Editor(Entity<Editor>, bool),
 105    Terminal(Entity<TerminalView>),
 106}
 107
 108impl AssistantPanel {
 109    pub fn load(
 110        workspace: WeakEntity<Workspace>,
 111        prompt_builder: Arc<PromptBuilder>,
 112        cx: AsyncWindowContext,
 113    ) -> Task<Result<Entity<Self>>> {
 114        cx.spawn(async move |cx| {
 115            let slash_commands = Arc::new(SlashCommandWorkingSet::default());
 116            let context_store = workspace
 117                .update(cx, |workspace, cx| {
 118                    let project = workspace.project().clone();
 119                    ContextStore::new(project, prompt_builder.clone(), slash_commands, cx)
 120                })?
 121                .await?;
 122
 123            workspace.update_in(cx, |workspace, window, cx| {
 124                // TODO: deserialize state.
 125                cx.new(|cx| Self::new(workspace, context_store, window, cx))
 126            })
 127        })
 128    }
 129
 130    fn new(
 131        workspace: &Workspace,
 132        context_store: Entity<ContextStore>,
 133        window: &mut Window,
 134        cx: &mut Context<Self>,
 135    ) -> Self {
 136        let model_summary_editor = cx.new(|cx| Editor::single_line(window, cx));
 137        let context_editor_toolbar =
 138            cx.new(|_| ContextEditorToolbarItem::new(model_summary_editor.clone()));
 139
 140        let pane = cx.new(|cx| {
 141            let mut pane = Pane::new(
 142                workspace.weak_handle(),
 143                workspace.project().clone(),
 144                Default::default(),
 145                None,
 146                NewChat.boxed_clone(),
 147                window,
 148                cx,
 149            );
 150
 151            let project = workspace.project().clone();
 152            pane.set_custom_drop_handle(cx, move |_, dropped_item, window, cx| {
 153                let action = maybe!({
 154                    if project.read(cx).is_local() {
 155                        if let Some(paths) = dropped_item.downcast_ref::<ExternalPaths>() {
 156                            return Some(InsertDraggedFiles::ExternalFiles(paths.paths().to_vec()));
 157                        }
 158                    }
 159
 160                    let project_paths = if let Some(tab) = dropped_item.downcast_ref::<DraggedTab>()
 161                    {
 162                        if tab.pane == cx.entity() {
 163                            return None;
 164                        }
 165                        let item = tab.pane.read(cx).item_for_index(tab.ix);
 166                        Some(
 167                            item.and_then(|item| item.project_path(cx))
 168                                .into_iter()
 169                                .collect::<Vec<_>>(),
 170                        )
 171                    } else if let Some(selection) = dropped_item.downcast_ref::<DraggedSelection>()
 172                    {
 173                        Some(
 174                            selection
 175                                .items()
 176                                .filter_map(|item| {
 177                                    project.read(cx).path_for_entry(item.entry_id, cx)
 178                                })
 179                                .collect::<Vec<_>>(),
 180                        )
 181                    } else {
 182                        None
 183                    }?;
 184
 185                    let paths = project_paths
 186                        .into_iter()
 187                        .filter_map(|project_path| {
 188                            let worktree = project
 189                                .read(cx)
 190                                .worktree_for_id(project_path.worktree_id, cx)?;
 191
 192                            let mut full_path = PathBuf::from(worktree.read(cx).root_name());
 193                            full_path.push(&project_path.path);
 194                            Some(full_path)
 195                        })
 196                        .collect::<Vec<_>>();
 197
 198                    Some(InsertDraggedFiles::ProjectPaths(paths))
 199                });
 200
 201                if let Some(action) = action {
 202                    window.dispatch_action(action.boxed_clone(), cx);
 203                }
 204
 205                ControlFlow::Break(())
 206            });
 207
 208            pane.set_can_navigate(true, cx);
 209            pane.display_nav_history_buttons(None);
 210            pane.set_should_display_tab_bar(|_, _| true);
 211            pane.set_render_tab_bar_buttons(cx, move |pane, _window, cx| {
 212                let focus_handle = pane.focus_handle(cx);
 213                let left_children = IconButton::new("history", IconName::HistoryRerun)
 214                    .icon_size(IconSize::Small)
 215                    .on_click(cx.listener({
 216                        let focus_handle = focus_handle.clone();
 217                        move |_, _, window, cx| {
 218                            focus_handle.focus(window);
 219                            window.dispatch_action(DeployHistory.boxed_clone(), cx)
 220                        }
 221                    }))
 222                    .tooltip({
 223                        let focus_handle = focus_handle.clone();
 224                        move |window, cx| {
 225                            Tooltip::for_action_in(
 226                                "Open History",
 227                                &DeployHistory,
 228                                &focus_handle,
 229                                window,
 230                                cx,
 231                            )
 232                        }
 233                    })
 234                    .toggle_state(
 235                        pane.active_item()
 236                            .map_or(false, |item| item.downcast::<ContextHistory>().is_some()),
 237                    );
 238                let _pane = cx.entity().clone();
 239                let right_children = h_flex()
 240                    .gap(DynamicSpacing::Base02.rems(cx))
 241                    .child(
 242                        IconButton::new("new-chat", IconName::Plus)
 243                            .icon_size(IconSize::Small)
 244                            .on_click(cx.listener(|_, _, window, cx| {
 245                                window.dispatch_action(NewChat.boxed_clone(), cx)
 246                            }))
 247                            .tooltip(move |window, cx| {
 248                                Tooltip::for_action_in(
 249                                    "New Chat",
 250                                    &NewChat,
 251                                    &focus_handle,
 252                                    window,
 253                                    cx,
 254                                )
 255                            }),
 256                    )
 257                    .child(
 258                        PopoverMenu::new("assistant-panel-popover-menu")
 259                            .trigger_with_tooltip(
 260                                IconButton::new("menu", IconName::EllipsisVertical)
 261                                    .icon_size(IconSize::Small),
 262                                Tooltip::text("Toggle Assistant Menu"),
 263                            )
 264                            .menu(move |window, cx| {
 265                                let zoom_label = if _pane.read(cx).is_zoomed() {
 266                                    "Zoom Out"
 267                                } else {
 268                                    "Zoom In"
 269                                };
 270                                let focus_handle = _pane.focus_handle(cx);
 271                                Some(ContextMenu::build(window, cx, move |menu, _, _| {
 272                                    menu.context(focus_handle.clone())
 273                                        .action("New Chat", Box::new(NewChat))
 274                                        .action("History", Box::new(DeployHistory))
 275                                        .action("Prompt Library", Box::new(OpenPromptLibrary))
 276                                        .action("Configure", Box::new(ShowConfiguration))
 277                                        .action(zoom_label, Box::new(ToggleZoom))
 278                                }))
 279                            }),
 280                    )
 281                    .into_any_element()
 282                    .into();
 283
 284                (Some(left_children.into_any_element()), right_children)
 285            });
 286            pane.toolbar().update(cx, |toolbar, cx| {
 287                toolbar.add_item(context_editor_toolbar.clone(), window, cx);
 288                toolbar.add_item(
 289                    cx.new(|cx| {
 290                        BufferSearchBar::new(
 291                            Some(workspace.project().read(cx).languages().clone()),
 292                            window,
 293                            cx,
 294                        )
 295                    }),
 296                    window,
 297                    cx,
 298                )
 299            });
 300            pane
 301        });
 302
 303        let subscriptions = vec![
 304            cx.observe(&pane, |_, _, cx| cx.notify()),
 305            cx.subscribe_in(&pane, window, Self::handle_pane_event),
 306            cx.subscribe(&context_editor_toolbar, Self::handle_toolbar_event),
 307            cx.subscribe(&model_summary_editor, Self::handle_summary_editor_event),
 308            cx.subscribe_in(&context_store, window, Self::handle_context_store_event),
 309            cx.subscribe_in(
 310                &LanguageModelRegistry::global(cx),
 311                window,
 312                |this, _, event: &language_model::Event, window, cx| match event {
 313                    language_model::Event::DefaultModelChanged
 314                    | language_model::Event::InlineAssistantModelChanged
 315                    | language_model::Event::CommitMessageModelChanged
 316                    | language_model::Event::ThreadSummaryModelChanged => {
 317                        this.completion_provider_changed(window, cx);
 318                    }
 319                    language_model::Event::ProviderStateChanged => {
 320                        this.ensure_authenticated(window, cx);
 321                        cx.notify()
 322                    }
 323                    language_model::Event::AddedProvider(_)
 324                    | language_model::Event::RemovedProvider(_) => {
 325                        this.ensure_authenticated(window, cx);
 326                    }
 327                },
 328            ),
 329        ];
 330
 331        let watch_client_status = Self::watch_client_status(workspace.client().clone(), window, cx);
 332
 333        let mut this = Self {
 334            pane,
 335            workspace: workspace.weak_handle(),
 336            width: None,
 337            height: None,
 338            project: workspace.project().clone(),
 339            context_store,
 340            languages: workspace.app_state().languages.clone(),
 341            fs: workspace.app_state().fs.clone(),
 342            subscriptions,
 343            model_summary_editor,
 344            authenticate_provider_task: None,
 345            configuration_subscription: None,
 346            client_status: None,
 347            watch_client_status: Some(watch_client_status),
 348            show_zed_ai_notice: false,
 349        };
 350        this.new_context(window, cx);
 351        this
 352    }
 353
 354    pub fn toggle_focus(
 355        workspace: &mut Workspace,
 356        _: &ToggleFocus,
 357        window: &mut Window,
 358        cx: &mut Context<Workspace>,
 359    ) {
 360        if workspace
 361            .panel::<Self>(cx)
 362            .is_some_and(|panel| panel.read(cx).enabled(cx))
 363        {
 364            workspace.toggle_panel_focus::<Self>(window, cx);
 365        }
 366    }
 367
 368    fn watch_client_status(
 369        client: Arc<Client>,
 370        window: &mut Window,
 371        cx: &mut Context<Self>,
 372    ) -> Task<()> {
 373        let mut status_rx = client.status();
 374
 375        cx.spawn_in(window, async move |this, cx| {
 376            while let Some(status) = status_rx.next().await {
 377                this.update(cx, |this, cx| {
 378                    if this.client_status.is_none()
 379                        || this
 380                            .client_status
 381                            .map_or(false, |old_status| old_status != status)
 382                    {
 383                        this.update_zed_ai_notice_visibility(status, cx);
 384                    }
 385                    this.client_status = Some(status);
 386                })
 387                .log_err();
 388            }
 389            this.update(cx, |this, _cx| this.watch_client_status = None)
 390                .log_err();
 391        })
 392    }
 393
 394    fn handle_pane_event(
 395        &mut self,
 396        pane: &Entity<Pane>,
 397        event: &pane::Event,
 398        window: &mut Window,
 399        cx: &mut Context<Self>,
 400    ) {
 401        let update_model_summary = match event {
 402            pane::Event::Remove { .. } => {
 403                cx.emit(PanelEvent::Close);
 404                false
 405            }
 406            pane::Event::ZoomIn => {
 407                cx.emit(PanelEvent::ZoomIn);
 408                false
 409            }
 410            pane::Event::ZoomOut => {
 411                cx.emit(PanelEvent::ZoomOut);
 412                false
 413            }
 414
 415            pane::Event::AddItem { item } => {
 416                self.workspace
 417                    .update(cx, |workspace, cx| {
 418                        item.added_to_pane(workspace, self.pane.clone(), window, cx)
 419                    })
 420                    .ok();
 421                true
 422            }
 423
 424            pane::Event::ActivateItem { local, .. } => {
 425                if *local {
 426                    self.workspace
 427                        .update(cx, |workspace, cx| {
 428                            workspace.unfollow_in_pane(&pane, window, cx);
 429                        })
 430                        .ok();
 431                }
 432                cx.emit(AssistantPanelEvent::ContextEdited);
 433                true
 434            }
 435            pane::Event::RemovedItem { .. } => {
 436                let has_configuration_view = self
 437                    .pane
 438                    .read(cx)
 439                    .items_of_type::<ConfigurationView>()
 440                    .next()
 441                    .is_some();
 442
 443                if !has_configuration_view {
 444                    self.configuration_subscription = None;
 445                }
 446
 447                cx.emit(AssistantPanelEvent::ContextEdited);
 448                true
 449            }
 450
 451            _ => false,
 452        };
 453
 454        if update_model_summary {
 455            if let Some(editor) = self.active_context_editor(cx) {
 456                self.show_updated_summary(&editor, window, cx)
 457            }
 458        }
 459    }
 460
 461    fn handle_summary_editor_event(
 462        &mut self,
 463        model_summary_editor: Entity<Editor>,
 464        event: &EditorEvent,
 465        cx: &mut Context<Self>,
 466    ) {
 467        if matches!(event, EditorEvent::Edited { .. }) {
 468            if let Some(context_editor) = self.active_context_editor(cx) {
 469                let new_summary = model_summary_editor.read(cx).text(cx);
 470                context_editor.update(cx, |context_editor, cx| {
 471                    context_editor.context().update(cx, |context, cx| {
 472                        if context.summary().is_none()
 473                            && (new_summary == DEFAULT_TAB_TITLE || new_summary.trim().is_empty())
 474                        {
 475                            return;
 476                        }
 477                        context.custom_summary(new_summary, cx)
 478                    });
 479                });
 480            }
 481        }
 482    }
 483
 484    fn update_zed_ai_notice_visibility(&mut self, client_status: Status, cx: &mut Context<Self>) {
 485        let model = LanguageModelRegistry::read_global(cx).default_model();
 486
 487        // If we're signed out and don't have a provider configured, or we're signed-out AND Zed.dev is
 488        // the provider, we want to show a nudge to sign in.
 489        let show_zed_ai_notice = client_status.is_signed_out()
 490            && model.map_or(true, |model| model.provider.id().0 == ZED_CLOUD_PROVIDER_ID);
 491
 492        self.show_zed_ai_notice = show_zed_ai_notice;
 493        cx.notify();
 494    }
 495
 496    fn handle_toolbar_event(
 497        &mut self,
 498        _: Entity<ContextEditorToolbarItem>,
 499        _: &ContextEditorToolbarItemEvent,
 500        cx: &mut Context<Self>,
 501    ) {
 502        if let Some(context_editor) = self.active_context_editor(cx) {
 503            context_editor.update(cx, |context_editor, cx| {
 504                context_editor.context().update(cx, |context, cx| {
 505                    context.summarize(true, cx);
 506                })
 507            })
 508        }
 509    }
 510
 511    fn handle_context_store_event(
 512        &mut self,
 513        _context_store: &Entity<ContextStore>,
 514        event: &ContextStoreEvent,
 515        window: &mut Window,
 516        cx: &mut Context<Self>,
 517    ) {
 518        let ContextStoreEvent::ContextCreated(context_id) = event;
 519        let Some(context) = self
 520            .context_store
 521            .read(cx)
 522            .loaded_context_for_id(&context_id, cx)
 523        else {
 524            log::error!("no context found with ID: {}", context_id.to_proto());
 525            return;
 526        };
 527        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
 528            .log_err()
 529            .flatten();
 530
 531        let editor = cx.new(|cx| {
 532            let mut editor = ContextEditor::for_context(
 533                context,
 534                self.fs.clone(),
 535                self.workspace.clone(),
 536                self.project.clone(),
 537                lsp_adapter_delegate,
 538                window,
 539                cx,
 540            );
 541            editor.insert_default_prompt(window, cx);
 542            editor
 543        });
 544
 545        self.show_context(editor.clone(), window, cx);
 546    }
 547
 548    fn completion_provider_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 549        if let Some(editor) = self.active_context_editor(cx) {
 550            editor.update(cx, |active_context, cx| {
 551                active_context
 552                    .context()
 553                    .update(cx, |context, cx| context.completion_provider_changed(cx))
 554            })
 555        }
 556
 557        let Some(new_provider_id) = LanguageModelRegistry::read_global(cx)
 558            .default_model()
 559            .map(|default| default.provider.id())
 560        else {
 561            return;
 562        };
 563
 564        if self
 565            .authenticate_provider_task
 566            .as_ref()
 567            .map_or(true, |(old_provider_id, _)| {
 568                *old_provider_id != new_provider_id
 569            })
 570        {
 571            self.authenticate_provider_task = None;
 572            self.ensure_authenticated(window, cx);
 573        }
 574
 575        if let Some(status) = self.client_status {
 576            self.update_zed_ai_notice_visibility(status, cx);
 577        }
 578    }
 579
 580    fn ensure_authenticated(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 581        if self.is_authenticated(cx) {
 582            return;
 583        }
 584
 585        let Some(ConfiguredModel { provider, .. }) =
 586            LanguageModelRegistry::read_global(cx).default_model()
 587        else {
 588            return;
 589        };
 590
 591        let load_credentials = self.authenticate(cx);
 592
 593        if self.authenticate_provider_task.is_none() {
 594            self.authenticate_provider_task = Some((
 595                provider.id(),
 596                cx.spawn_in(window, async move |this, cx| {
 597                    if let Some(future) = load_credentials {
 598                        let _ = future.await;
 599                    }
 600                    this.update(cx, |this, _cx| {
 601                        this.authenticate_provider_task = None;
 602                    })
 603                    .log_err();
 604                }),
 605            ));
 606        }
 607    }
 608
 609    pub fn inline_assist(
 610        workspace: &mut Workspace,
 611        action: &InlineAssist,
 612        window: &mut Window,
 613        cx: &mut Context<Workspace>,
 614    ) {
 615        let Some(assistant_panel) = workspace
 616            .panel::<AssistantPanel>(cx)
 617            .filter(|panel| panel.read(cx).enabled(cx))
 618        else {
 619            return;
 620        };
 621
 622        let Some(inline_assist_target) =
 623            Self::resolve_inline_assist_target(workspace, &assistant_panel, window, cx)
 624        else {
 625            return;
 626        };
 627
 628        let initial_prompt = action.prompt.clone();
 629
 630        if assistant_panel.update(cx, |assistant, cx| assistant.is_authenticated(cx)) {
 631            match inline_assist_target {
 632                InlineAssistTarget::Editor(active_editor, include_context) => {
 633                    InlineAssistant::update_global(cx, |assistant, cx| {
 634                        assistant.assist(
 635                            &active_editor,
 636                            Some(cx.entity().downgrade()),
 637                            include_context.then_some(&assistant_panel),
 638                            initial_prompt,
 639                            window,
 640                            cx,
 641                        )
 642                    })
 643                }
 644                InlineAssistTarget::Terminal(active_terminal) => {
 645                    TerminalInlineAssistant::update_global(cx, |assistant, cx| {
 646                        assistant.assist(
 647                            &active_terminal,
 648                            Some(cx.entity().downgrade()),
 649                            Some(&assistant_panel),
 650                            initial_prompt,
 651                            window,
 652                            cx,
 653                        )
 654                    })
 655                }
 656            }
 657        } else {
 658            let assistant_panel = assistant_panel.downgrade();
 659            cx.spawn_in(window, async move |workspace, cx| {
 660                let Some(task) =
 661                    assistant_panel.update(cx, |assistant, cx| assistant.authenticate(cx))?
 662                else {
 663                    let answer = cx
 664                        .prompt(
 665                            gpui::PromptLevel::Warning,
 666                            "No language model provider configured",
 667                            None,
 668                            &["Configure", "Cancel"],
 669                        )
 670                        .await
 671                        .ok();
 672                    if let Some(answer) = answer {
 673                        if answer == 0 {
 674                            cx.update(|window, cx| {
 675                                window.dispatch_action(Box::new(ShowConfiguration), cx)
 676                            })
 677                            .ok();
 678                        }
 679                    }
 680                    return Ok(());
 681                };
 682                task.await?;
 683                if assistant_panel.update(cx, |panel, cx| panel.is_authenticated(cx))? {
 684                    cx.update(|window, cx| match inline_assist_target {
 685                        InlineAssistTarget::Editor(active_editor, include_context) => {
 686                            let assistant_panel = if include_context {
 687                                assistant_panel.upgrade()
 688                            } else {
 689                                None
 690                            };
 691                            InlineAssistant::update_global(cx, |assistant, cx| {
 692                                assistant.assist(
 693                                    &active_editor,
 694                                    Some(workspace),
 695                                    assistant_panel.as_ref(),
 696                                    initial_prompt,
 697                                    window,
 698                                    cx,
 699                                )
 700                            })
 701                        }
 702                        InlineAssistTarget::Terminal(active_terminal) => {
 703                            TerminalInlineAssistant::update_global(cx, |assistant, cx| {
 704                                assistant.assist(
 705                                    &active_terminal,
 706                                    Some(workspace),
 707                                    assistant_panel.upgrade().as_ref(),
 708                                    initial_prompt,
 709                                    window,
 710                                    cx,
 711                                )
 712                            })
 713                        }
 714                    })?
 715                } else {
 716                    workspace.update_in(cx, |workspace, window, cx| {
 717                        workspace.focus_panel::<AssistantPanel>(window, cx)
 718                    })?;
 719                }
 720
 721                anyhow::Ok(())
 722            })
 723            .detach_and_log_err(cx)
 724        }
 725    }
 726
 727    fn resolve_inline_assist_target(
 728        workspace: &mut Workspace,
 729        assistant_panel: &Entity<AssistantPanel>,
 730        window: &mut Window,
 731        cx: &mut App,
 732    ) -> Option<InlineAssistTarget> {
 733        if let Some(terminal_panel) = workspace.panel::<TerminalPanel>(cx) {
 734            if terminal_panel
 735                .read(cx)
 736                .focus_handle(cx)
 737                .contains_focused(window, cx)
 738            {
 739                if let Some(terminal_view) = terminal_panel.read(cx).pane().and_then(|pane| {
 740                    pane.read(cx)
 741                        .active_item()
 742                        .and_then(|t| t.downcast::<TerminalView>())
 743                }) {
 744                    return Some(InlineAssistTarget::Terminal(terminal_view));
 745                }
 746            }
 747        }
 748        let context_editor =
 749            assistant_panel
 750                .read(cx)
 751                .active_context_editor(cx)
 752                .and_then(|editor| {
 753                    let editor = &editor.read(cx).editor().clone();
 754                    if editor.read(cx).is_focused(window) {
 755                        Some(editor.clone())
 756                    } else {
 757                        None
 758                    }
 759                });
 760
 761        if let Some(context_editor) = context_editor {
 762            Some(InlineAssistTarget::Editor(context_editor, false))
 763        } else if let Some(workspace_editor) = workspace
 764            .active_item(cx)
 765            .and_then(|item| item.act_as::<Editor>(cx))
 766        {
 767            Some(InlineAssistTarget::Editor(workspace_editor, true))
 768        } else if let Some(terminal_view) = workspace
 769            .active_item(cx)
 770            .and_then(|item| item.act_as::<TerminalView>(cx))
 771        {
 772            Some(InlineAssistTarget::Terminal(terminal_view))
 773        } else {
 774            None
 775        }
 776    }
 777
 778    pub fn create_new_context(
 779        workspace: &mut Workspace,
 780        _: &NewChat,
 781        window: &mut Window,
 782        cx: &mut Context<Workspace>,
 783    ) {
 784        if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
 785            let did_create_context = panel
 786                .update(cx, |panel, cx| {
 787                    panel.new_context(window, cx)?;
 788
 789                    Some(())
 790                })
 791                .is_some();
 792            if did_create_context {
 793                ContextEditor::quote_selection(workspace, &Default::default(), window, cx);
 794            }
 795        }
 796    }
 797
 798    pub fn new_context(
 799        &mut self,
 800        window: &mut Window,
 801        cx: &mut Context<Self>,
 802    ) -> Option<Entity<ContextEditor>> {
 803        let project = self.project.read(cx);
 804        if project.is_via_collab() {
 805            let task = self
 806                .context_store
 807                .update(cx, |store, cx| store.create_remote_context(cx));
 808
 809            cx.spawn_in(window, async move |this, cx| {
 810                let context = task.await?;
 811
 812                this.update_in(cx, |this, window, cx| {
 813                    let workspace = this.workspace.clone();
 814                    let project = this.project.clone();
 815                    let lsp_adapter_delegate =
 816                        make_lsp_adapter_delegate(&project, cx).log_err().flatten();
 817
 818                    let fs = this.fs.clone();
 819                    let project = this.project.clone();
 820
 821                    let editor = cx.new(|cx| {
 822                        ContextEditor::for_context(
 823                            context,
 824                            fs,
 825                            workspace,
 826                            project,
 827                            lsp_adapter_delegate,
 828                            window,
 829                            cx,
 830                        )
 831                    });
 832
 833                    this.show_context(editor, window, cx);
 834
 835                    anyhow::Ok(())
 836                })??;
 837
 838                anyhow::Ok(())
 839            })
 840            .detach_and_log_err(cx);
 841
 842            None
 843        } else {
 844            let context = self.context_store.update(cx, |store, cx| store.create(cx));
 845            let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
 846                .log_err()
 847                .flatten();
 848
 849            let editor = cx.new(|cx| {
 850                let mut editor = ContextEditor::for_context(
 851                    context,
 852                    self.fs.clone(),
 853                    self.workspace.clone(),
 854                    self.project.clone(),
 855                    lsp_adapter_delegate,
 856                    window,
 857                    cx,
 858                );
 859                editor.insert_default_prompt(window, cx);
 860                editor
 861            });
 862
 863            self.show_context(editor.clone(), window, cx);
 864            let workspace = self.workspace.clone();
 865            cx.spawn_in(window, async move |_, cx| {
 866                workspace
 867                    .update_in(cx, |workspace, window, cx| {
 868                        workspace.focus_panel::<AssistantPanel>(window, cx);
 869                    })
 870                    .ok();
 871            })
 872            .detach();
 873            Some(editor)
 874        }
 875    }
 876
 877    fn show_context(
 878        &mut self,
 879        context_editor: Entity<ContextEditor>,
 880        window: &mut Window,
 881        cx: &mut Context<Self>,
 882    ) {
 883        let focus = self.focus_handle(cx).contains_focused(window, cx);
 884        let prev_len = self.pane.read(cx).items_len();
 885        self.pane.update(cx, |pane, cx| {
 886            pane.add_item(
 887                Box::new(context_editor.clone()),
 888                focus,
 889                focus,
 890                None,
 891                window,
 892                cx,
 893            )
 894        });
 895
 896        if prev_len != self.pane.read(cx).items_len() {
 897            self.subscriptions.push(cx.subscribe_in(
 898                &context_editor,
 899                window,
 900                Self::handle_context_editor_event,
 901            ));
 902        }
 903
 904        self.show_updated_summary(&context_editor, window, cx);
 905
 906        cx.emit(AssistantPanelEvent::ContextEdited);
 907        cx.notify();
 908    }
 909
 910    fn show_updated_summary(
 911        &self,
 912        context_editor: &Entity<ContextEditor>,
 913        window: &mut Window,
 914        cx: &mut Context<Self>,
 915    ) {
 916        context_editor.update(cx, |context_editor, cx| {
 917            let new_summary = context_editor.title(cx).to_string();
 918            self.model_summary_editor.update(cx, |summary_editor, cx| {
 919                if summary_editor.text(cx) != new_summary {
 920                    summary_editor.set_text(new_summary, window, cx);
 921                }
 922            });
 923        });
 924    }
 925
 926    fn handle_context_editor_event(
 927        &mut self,
 928        context_editor: &Entity<ContextEditor>,
 929        event: &EditorEvent,
 930        window: &mut Window,
 931        cx: &mut Context<Self>,
 932    ) {
 933        match event {
 934            EditorEvent::TitleChanged => {
 935                self.show_updated_summary(&context_editor, window, cx);
 936                cx.notify()
 937            }
 938            EditorEvent::Edited { .. } => {
 939                self.workspace
 940                    .update(cx, |workspace, cx| {
 941                        let is_via_ssh = workspace
 942                            .project()
 943                            .update(cx, |project, _| project.is_via_ssh());
 944
 945                        workspace
 946                            .client()
 947                            .telemetry()
 948                            .log_edit_event("assistant panel", is_via_ssh);
 949                    })
 950                    .log_err();
 951                cx.emit(AssistantPanelEvent::ContextEdited)
 952            }
 953            _ => {}
 954        }
 955    }
 956
 957    fn show_configuration(
 958        workspace: &mut Workspace,
 959        _: &ShowConfiguration,
 960        window: &mut Window,
 961        cx: &mut Context<Workspace>,
 962    ) {
 963        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
 964            return;
 965        };
 966
 967        if !panel.focus_handle(cx).contains_focused(window, cx) {
 968            workspace.toggle_panel_focus::<AssistantPanel>(window, cx);
 969        }
 970
 971        panel.update(cx, |this, cx| {
 972            this.show_configuration_tab(window, cx);
 973        })
 974    }
 975
 976    fn show_configuration_tab(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 977        let configuration_item_ix = self
 978            .pane
 979            .read(cx)
 980            .items()
 981            .position(|item| item.downcast::<ConfigurationView>().is_some());
 982
 983        if let Some(configuration_item_ix) = configuration_item_ix {
 984            self.pane.update(cx, |pane, cx| {
 985                pane.activate_item(configuration_item_ix, true, true, window, cx);
 986            });
 987        } else {
 988            let configuration = cx.new(|cx| ConfigurationView::new(window, cx));
 989            self.configuration_subscription = Some(cx.subscribe_in(
 990                &configuration,
 991                window,
 992                |this, _, event: &ConfigurationViewEvent, window, cx| match event {
 993                    ConfigurationViewEvent::NewProviderContextEditor(provider) => {
 994                        if LanguageModelRegistry::read_global(cx)
 995                            .default_model()
 996                            .map_or(true, |default| default.provider.id() != provider.id())
 997                        {
 998                            if let Some(model) = provider.default_model(cx) {
 999                                update_settings_file::<AssistantSettings>(
1000                                    this.fs.clone(),
1001                                    cx,
1002                                    move |settings, _| settings.set_model(model),
1003                                );
1004                            }
1005                        }
1006
1007                        this.new_context(window, cx);
1008                    }
1009                },
1010            ));
1011            self.pane.update(cx, |pane, cx| {
1012                pane.add_item(Box::new(configuration), true, true, None, window, cx);
1013            });
1014        }
1015    }
1016
1017    fn deploy_history(&mut self, _: &DeployHistory, window: &mut Window, cx: &mut Context<Self>) {
1018        let history_item_ix = self
1019            .pane
1020            .read(cx)
1021            .items()
1022            .position(|item| item.downcast::<ContextHistory>().is_some());
1023
1024        if let Some(history_item_ix) = history_item_ix {
1025            self.pane.update(cx, |pane, cx| {
1026                pane.activate_item(history_item_ix, true, true, window, cx);
1027            });
1028        } else {
1029            let history = cx.new(|cx| {
1030                ContextHistory::new(
1031                    self.project.clone(),
1032                    self.context_store.clone(),
1033                    self.workspace.clone(),
1034                    window,
1035                    cx,
1036                )
1037            });
1038            self.pane.update(cx, |pane, cx| {
1039                pane.add_item(Box::new(history), true, true, None, window, cx);
1040            });
1041        }
1042    }
1043
1044    fn deploy_prompt_library(
1045        &mut self,
1046        _: &OpenPromptLibrary,
1047        _window: &mut Window,
1048        cx: &mut Context<Self>,
1049    ) {
1050        open_prompt_library(
1051            self.languages.clone(),
1052            Box::new(PromptLibraryInlineAssist),
1053            Arc::new(|| {
1054                Box::new(SlashCommandCompletionProvider::new(
1055                    Arc::new(SlashCommandWorkingSet::default()),
1056                    None,
1057                    None,
1058                ))
1059            }),
1060            cx,
1061        )
1062        .detach_and_log_err(cx);
1063    }
1064
1065    pub(crate) fn active_context_editor(&self, cx: &App) -> Option<Entity<ContextEditor>> {
1066        self.pane
1067            .read(cx)
1068            .active_item()?
1069            .downcast::<ContextEditor>()
1070    }
1071
1072    pub fn active_context(&self, cx: &App) -> Option<Entity<AssistantContext>> {
1073        Some(self.active_context_editor(cx)?.read(cx).context().clone())
1074    }
1075
1076    pub fn open_saved_context(
1077        &mut self,
1078        path: PathBuf,
1079        window: &mut Window,
1080        cx: &mut Context<Self>,
1081    ) -> Task<Result<()>> {
1082        let existing_context = self.pane.read(cx).items().find_map(|item| {
1083            item.downcast::<ContextEditor>()
1084                .filter(|editor| editor.read(cx).context().read(cx).path() == Some(&path))
1085        });
1086        if let Some(existing_context) = existing_context {
1087            return cx.spawn_in(window, async move |this, cx| {
1088                this.update_in(cx, |this, window, cx| {
1089                    this.show_context(existing_context, window, cx)
1090                })
1091            });
1092        }
1093
1094        let context = self
1095            .context_store
1096            .update(cx, |store, cx| store.open_local_context(path.clone(), cx));
1097        let fs = self.fs.clone();
1098        let project = self.project.clone();
1099        let workspace = self.workspace.clone();
1100
1101        let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();
1102
1103        cx.spawn_in(window, async move |this, cx| {
1104            let context = context.await?;
1105            this.update_in(cx, |this, window, cx| {
1106                let editor = cx.new(|cx| {
1107                    ContextEditor::for_context(
1108                        context,
1109                        fs,
1110                        workspace,
1111                        project,
1112                        lsp_adapter_delegate,
1113                        window,
1114                        cx,
1115                    )
1116                });
1117                this.show_context(editor, window, cx);
1118                anyhow::Ok(())
1119            })??;
1120            Ok(())
1121        })
1122    }
1123
1124    pub fn open_remote_context(
1125        &mut self,
1126        id: ContextId,
1127        window: &mut Window,
1128        cx: &mut Context<Self>,
1129    ) -> Task<Result<Entity<ContextEditor>>> {
1130        let existing_context = self.pane.read(cx).items().find_map(|item| {
1131            item.downcast::<ContextEditor>()
1132                .filter(|editor| *editor.read(cx).context().read(cx).id() == id)
1133        });
1134        if let Some(existing_context) = existing_context {
1135            return cx.spawn_in(window, async move |this, cx| {
1136                this.update_in(cx, |this, window, cx| {
1137                    this.show_context(existing_context.clone(), window, cx)
1138                })?;
1139                Ok(existing_context)
1140            });
1141        }
1142
1143        let context = self
1144            .context_store
1145            .update(cx, |store, cx| store.open_remote_context(id, cx));
1146        let fs = self.fs.clone();
1147        let workspace = self.workspace.clone();
1148        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
1149            .log_err()
1150            .flatten();
1151
1152        cx.spawn_in(window, async move |this, cx| {
1153            let context = context.await?;
1154            this.update_in(cx, |this, window, cx| {
1155                let editor = cx.new(|cx| {
1156                    ContextEditor::for_context(
1157                        context,
1158                        fs,
1159                        workspace,
1160                        this.project.clone(),
1161                        lsp_adapter_delegate,
1162                        window,
1163                        cx,
1164                    )
1165                });
1166                this.show_context(editor.clone(), window, cx);
1167                anyhow::Ok(editor)
1168            })?
1169        })
1170    }
1171
1172    fn is_authenticated(&mut self, cx: &mut Context<Self>) -> bool {
1173        LanguageModelRegistry::read_global(cx)
1174            .default_model()
1175            .map_or(false, |default| default.provider.is_authenticated(cx))
1176    }
1177
1178    fn authenticate(
1179        &mut self,
1180        cx: &mut Context<Self>,
1181    ) -> Option<Task<Result<(), AuthenticateError>>> {
1182        LanguageModelRegistry::read_global(cx)
1183            .default_model()
1184            .map_or(None, |default| Some(default.provider.authenticate(cx)))
1185    }
1186
1187    fn restart_context_servers(
1188        workspace: &mut Workspace,
1189        _action: &context_server::Restart,
1190        _: &mut Window,
1191        cx: &mut Context<Workspace>,
1192    ) {
1193        let Some(assistant_panel) = workspace.panel::<AssistantPanel>(cx) else {
1194            return;
1195        };
1196
1197        assistant_panel.update(cx, |assistant_panel, cx| {
1198            assistant_panel
1199                .context_store
1200                .update(cx, |context_store, cx| {
1201                    context_store.restart_context_servers(cx);
1202                });
1203        });
1204    }
1205}
1206
1207impl Render for AssistantPanel {
1208    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1209        let mut registrar = DivRegistrar::new(
1210            |panel, _, cx| {
1211                panel
1212                    .pane
1213                    .read(cx)
1214                    .toolbar()
1215                    .read(cx)
1216                    .item_of_type::<BufferSearchBar>()
1217            },
1218            cx,
1219        );
1220        BufferSearchBar::register(&mut registrar);
1221        let registrar = registrar.into_div();
1222
1223        v_flex()
1224            .key_context("AssistantPanel")
1225            .size_full()
1226            .on_action(cx.listener(|this, _: &NewChat, window, cx| {
1227                this.new_context(window, cx);
1228            }))
1229            .on_action(cx.listener(|this, _: &ShowConfiguration, window, cx| {
1230                this.show_configuration_tab(window, cx)
1231            }))
1232            .on_action(cx.listener(AssistantPanel::deploy_history))
1233            .on_action(cx.listener(AssistantPanel::deploy_prompt_library))
1234            .child(registrar.size_full().child(self.pane.clone()))
1235            .into_any_element()
1236    }
1237}
1238
1239impl Panel for AssistantPanel {
1240    fn persistent_name() -> &'static str {
1241        "AssistantPanel"
1242    }
1243
1244    fn position(&self, _: &Window, cx: &App) -> DockPosition {
1245        match AssistantSettings::get_global(cx).dock {
1246            AssistantDockPosition::Left => DockPosition::Left,
1247            AssistantDockPosition::Bottom => DockPosition::Bottom,
1248            AssistantDockPosition::Right => DockPosition::Right,
1249        }
1250    }
1251
1252    fn position_is_valid(&self, _: DockPosition) -> bool {
1253        true
1254    }
1255
1256    fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
1257        settings::update_settings_file::<AssistantSettings>(
1258            self.fs.clone(),
1259            cx,
1260            move |settings, _| {
1261                let dock = match position {
1262                    DockPosition::Left => AssistantDockPosition::Left,
1263                    DockPosition::Bottom => AssistantDockPosition::Bottom,
1264                    DockPosition::Right => AssistantDockPosition::Right,
1265                };
1266                settings.set_dock(dock);
1267            },
1268        );
1269    }
1270
1271    fn size(&self, window: &Window, cx: &App) -> Pixels {
1272        let settings = AssistantSettings::get_global(cx);
1273        match self.position(window, cx) {
1274            DockPosition::Left | DockPosition::Right => {
1275                self.width.unwrap_or(settings.default_width)
1276            }
1277            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
1278        }
1279    }
1280
1281    fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
1282        match self.position(window, cx) {
1283            DockPosition::Left | DockPosition::Right => self.width = size,
1284            DockPosition::Bottom => self.height = size,
1285        }
1286        cx.notify();
1287    }
1288
1289    fn is_zoomed(&self, _: &Window, cx: &App) -> bool {
1290        self.pane.read(cx).is_zoomed()
1291    }
1292
1293    fn set_zoomed(&mut self, zoomed: bool, _: &mut Window, cx: &mut Context<Self>) {
1294        self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx));
1295    }
1296
1297    fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut Context<Self>) {
1298        if active {
1299            if self.pane.read(cx).items_len() == 0 {
1300                self.new_context(window, cx);
1301            }
1302
1303            self.ensure_authenticated(window, cx);
1304        }
1305    }
1306
1307    fn pane(&self) -> Option<Entity<Pane>> {
1308        Some(self.pane.clone())
1309    }
1310
1311    fn remote_id() -> Option<proto::PanelId> {
1312        Some(proto::PanelId::AssistantPanel)
1313    }
1314
1315    fn icon(&self, _: &Window, cx: &App) -> Option<IconName> {
1316        (self.enabled(cx) && AssistantSettings::get_global(cx).button)
1317            .then_some(IconName::ZedAssistant)
1318    }
1319
1320    fn icon_tooltip(&self, _: &Window, _: &App) -> Option<&'static str> {
1321        Some("Assistant Panel")
1322    }
1323
1324    fn toggle_action(&self) -> Box<dyn Action> {
1325        Box::new(ToggleFocus)
1326    }
1327
1328    fn activation_priority(&self) -> u32 {
1329        4
1330    }
1331
1332    fn enabled(&self, cx: &App) -> bool {
1333        Assistant::enabled(cx)
1334    }
1335}
1336
1337impl EventEmitter<PanelEvent> for AssistantPanel {}
1338impl EventEmitter<AssistantPanelEvent> for AssistantPanel {}
1339
1340impl Focusable for AssistantPanel {
1341    fn focus_handle(&self, cx: &App) -> FocusHandle {
1342        self.pane.focus_handle(cx)
1343    }
1344}
1345
1346struct PromptLibraryInlineAssist;
1347
1348impl prompt_library::InlineAssistDelegate for PromptLibraryInlineAssist {
1349    fn assist(
1350        &self,
1351        prompt_editor: &Entity<Editor>,
1352        initial_prompt: Option<String>,
1353        window: &mut Window,
1354        cx: &mut Context<PromptLibrary>,
1355    ) {
1356        InlineAssistant::update_global(cx, |assistant, cx| {
1357            assistant.assist(&prompt_editor, None, None, initial_prompt, window, cx)
1358        })
1359    }
1360
1361    fn focus_assistant_panel(
1362        &self,
1363        workspace: &mut Workspace,
1364        window: &mut Window,
1365        cx: &mut Context<Workspace>,
1366    ) -> bool {
1367        workspace
1368            .focus_panel::<AssistantPanel>(window, cx)
1369            .is_some()
1370    }
1371}
1372
1373pub struct ConcreteAssistantPanelDelegate;
1374
1375impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
1376    fn active_context_editor(
1377        &self,
1378        workspace: &mut Workspace,
1379        _window: &mut Window,
1380        cx: &mut Context<Workspace>,
1381    ) -> Option<Entity<ContextEditor>> {
1382        let panel = workspace.panel::<AssistantPanel>(cx)?;
1383        panel.read(cx).active_context_editor(cx)
1384    }
1385
1386    fn open_saved_context(
1387        &self,
1388        workspace: &mut Workspace,
1389        path: PathBuf,
1390        window: &mut Window,
1391        cx: &mut Context<Workspace>,
1392    ) -> Task<Result<()>> {
1393        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
1394            return Task::ready(Err(anyhow!("no Assistant panel found")));
1395        };
1396
1397        panel.update(cx, |panel, cx| panel.open_saved_context(path, window, cx))
1398    }
1399
1400    fn open_remote_context(
1401        &self,
1402        workspace: &mut Workspace,
1403        context_id: ContextId,
1404        window: &mut Window,
1405        cx: &mut Context<Workspace>,
1406    ) -> Task<Result<Entity<ContextEditor>>> {
1407        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
1408            return Task::ready(Err(anyhow!("no Assistant panel found")));
1409        };
1410
1411        panel.update(cx, |panel, cx| {
1412            panel.open_remote_context(context_id, window, cx)
1413        })
1414    }
1415
1416    fn quote_selection(
1417        &self,
1418        workspace: &mut Workspace,
1419        selection_ranges: Vec<Range<Anchor>>,
1420        buffer: Entity<MultiBuffer>,
1421        window: &mut Window,
1422        cx: &mut Context<Workspace>,
1423    ) {
1424        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
1425            return;
1426        };
1427
1428        if !panel.focus_handle(cx).contains_focused(window, cx) {
1429            workspace.toggle_panel_focus::<AssistantPanel>(window, cx);
1430        }
1431
1432        let snapshot = buffer.read(cx).snapshot(cx);
1433        let selection_ranges = selection_ranges
1434            .into_iter()
1435            .map(|range| range.to_point(&snapshot))
1436            .collect::<Vec<_>>();
1437
1438        panel.update(cx, |_, cx| {
1439            // Wait to create a new context until the workspace is no longer
1440            // being updated.
1441            cx.defer_in(window, move |panel, window, cx| {
1442                if let Some(context) = panel
1443                    .active_context_editor(cx)
1444                    .or_else(|| panel.new_context(window, cx))
1445                {
1446                    context.update(cx, |context, cx| {
1447                        context.quote_ranges(selection_ranges, snapshot, window, cx)
1448                    });
1449                };
1450            });
1451        });
1452    }
1453}
1454
1455#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1456pub enum WorkflowAssistStatus {
1457    Pending,
1458    Confirmed,
1459    Done,
1460    Idle,
1461}