assistant_panel.rs

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