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