assistant_panel.rs

   1use std::path::PathBuf;
   2use std::sync::Arc;
   3
   4use anyhow::{Result, anyhow};
   5use assistant_context_editor::{
   6    AssistantPanelDelegate, ConfigurationError, ContextEditor, SlashCommandCompletionProvider,
   7    make_lsp_adapter_delegate, render_remaining_tokens,
   8};
   9use assistant_settings::{AssistantDockPosition, AssistantSettings};
  10use assistant_slash_command::SlashCommandWorkingSet;
  11use assistant_tool::ToolWorkingSet;
  12
  13use client::zed_urls;
  14use editor::{Editor, MultiBuffer};
  15use fs::Fs;
  16use gpui::{
  17    Action, AnyElement, App, AsyncWindowContext, Corner, Entity, EventEmitter, FocusHandle,
  18    Focusable, FontWeight, KeyContext, Pixels, Subscription, Task, UpdateGlobal, WeakEntity,
  19    action_with_deprecated_aliases, prelude::*,
  20};
  21use language::LanguageRegistry;
  22use language_model::{LanguageModelProviderTosView, LanguageModelRegistry};
  23use language_model_selector::ToggleModelSelector;
  24use project::Project;
  25use prompt_library::{PromptLibrary, open_prompt_library};
  26use prompt_store::PromptBuilder;
  27use settings::{Settings, update_settings_file};
  28use time::UtcOffset;
  29use ui::{
  30    Banner, ContextMenu, KeyBinding, PopoverMenu, PopoverMenuHandle, Tab, Tooltip, prelude::*,
  31};
  32use util::ResultExt as _;
  33use workspace::Workspace;
  34use workspace::dock::{DockPosition, Panel, PanelEvent};
  35use zed_actions::assistant::ToggleFocus;
  36
  37use crate::active_thread::ActiveThread;
  38use crate::assistant_configuration::{AssistantConfiguration, AssistantConfigurationEvent};
  39use crate::history_store::{HistoryEntry, HistoryStore};
  40use crate::message_editor::MessageEditor;
  41use crate::thread::{Thread, ThreadError, ThreadId};
  42use crate::thread_history::{PastContext, PastThread, ThreadHistory};
  43use crate::thread_store::ThreadStore;
  44use crate::{
  45    AgentDiff, InlineAssistant, NewPromptEditor, NewThread, OpenActiveThreadAsMarkdown,
  46    OpenAgentDiff, OpenConfiguration, OpenHistory, ToggleContextPicker,
  47};
  48
  49action_with_deprecated_aliases!(
  50    assistant,
  51    OpenPromptLibrary,
  52    ["assistant::DeployPromptLibrary"]
  53);
  54
  55pub fn init(cx: &mut App) {
  56    cx.observe_new(
  57        |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
  58            workspace
  59                .register_action(|workspace, action: &NewThread, window, cx| {
  60                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
  61                        panel.update(cx, |panel, cx| panel.new_thread(action, window, cx));
  62                        workspace.focus_panel::<AssistantPanel>(window, cx);
  63                    }
  64                })
  65                .register_action(|workspace, _: &OpenHistory, window, cx| {
  66                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
  67                        workspace.focus_panel::<AssistantPanel>(window, cx);
  68                        panel.update(cx, |panel, cx| panel.open_history(window, cx));
  69                    }
  70                })
  71                .register_action(|workspace, _: &OpenConfiguration, window, cx| {
  72                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
  73                        workspace.focus_panel::<AssistantPanel>(window, cx);
  74                        panel.update(cx, |panel, cx| panel.open_configuration(window, cx));
  75                    }
  76                })
  77                .register_action(|workspace, _: &NewPromptEditor, window, cx| {
  78                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
  79                        workspace.focus_panel::<AssistantPanel>(window, cx);
  80                        panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));
  81                    }
  82                })
  83                .register_action(|workspace, _: &OpenPromptLibrary, window, cx| {
  84                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
  85                        workspace.focus_panel::<AssistantPanel>(window, cx);
  86                        panel.update(cx, |panel, cx| {
  87                            panel.deploy_prompt_library(&OpenPromptLibrary, window, cx)
  88                        });
  89                    }
  90                })
  91                .register_action(|workspace, _: &OpenAgentDiff, window, cx| {
  92                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
  93                        workspace.focus_panel::<AssistantPanel>(window, cx);
  94                        panel.update(cx, |panel, cx| {
  95                            panel.open_agent_diff(&OpenAgentDiff, window, cx);
  96                        });
  97                    }
  98                });
  99        },
 100    )
 101    .detach();
 102}
 103
 104enum ActiveView {
 105    Thread,
 106    PromptEditor,
 107    History,
 108    Configuration,
 109}
 110
 111pub struct AssistantPanel {
 112    workspace: WeakEntity<Workspace>,
 113    project: Entity<Project>,
 114    fs: Arc<dyn Fs>,
 115    language_registry: Arc<LanguageRegistry>,
 116    thread_store: Entity<ThreadStore>,
 117    thread: Entity<ActiveThread>,
 118    message_editor: Entity<MessageEditor>,
 119    context_store: Entity<assistant_context_editor::ContextStore>,
 120    context_editor: Option<Entity<ContextEditor>>,
 121    configuration: Option<Entity<AssistantConfiguration>>,
 122    configuration_subscription: Option<Subscription>,
 123    local_timezone: UtcOffset,
 124    active_view: ActiveView,
 125    history_store: Entity<HistoryStore>,
 126    history: Entity<ThreadHistory>,
 127    assistant_dropdown_menu_handle: PopoverMenuHandle<ContextMenu>,
 128    width: Option<Pixels>,
 129    height: Option<Pixels>,
 130}
 131
 132impl AssistantPanel {
 133    pub fn load(
 134        workspace: WeakEntity<Workspace>,
 135        prompt_builder: Arc<PromptBuilder>,
 136        cx: AsyncWindowContext,
 137    ) -> Task<Result<Entity<Self>>> {
 138        cx.spawn(async move |cx| {
 139            let tools = Arc::new(ToolWorkingSet::default());
 140            let thread_store = workspace.update(cx, |workspace, cx| {
 141                let project = workspace.project().clone();
 142                ThreadStore::new(project, tools.clone(), prompt_builder.clone(), cx)
 143            })??;
 144
 145            let slash_commands = Arc::new(SlashCommandWorkingSet::default());
 146            let context_store = workspace
 147                .update(cx, |workspace, cx| {
 148                    let project = workspace.project().clone();
 149                    assistant_context_editor::ContextStore::new(
 150                        project,
 151                        prompt_builder.clone(),
 152                        slash_commands,
 153                        cx,
 154                    )
 155                })?
 156                .await?;
 157
 158            workspace.update_in(cx, |workspace, window, cx| {
 159                cx.new(|cx| Self::new(workspace, thread_store, context_store, window, cx))
 160            })
 161        })
 162    }
 163
 164    fn new(
 165        workspace: &Workspace,
 166        thread_store: Entity<ThreadStore>,
 167        context_store: Entity<assistant_context_editor::ContextStore>,
 168        window: &mut Window,
 169        cx: &mut Context<Self>,
 170    ) -> Self {
 171        let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
 172        let fs = workspace.app_state().fs.clone();
 173        let project = workspace.project().clone();
 174        let language_registry = project.read(cx).languages().clone();
 175        let workspace = workspace.weak_handle();
 176        let weak_self = cx.entity().downgrade();
 177
 178        let message_editor_context_store = cx.new(|_cx| {
 179            crate::context_store::ContextStore::new(
 180                workspace.clone(),
 181                Some(thread_store.downgrade()),
 182            )
 183        });
 184
 185        let message_editor = cx.new(|cx| {
 186            MessageEditor::new(
 187                fs.clone(),
 188                workspace.clone(),
 189                message_editor_context_store.clone(),
 190                thread_store.downgrade(),
 191                thread.clone(),
 192                window,
 193                cx,
 194            )
 195        });
 196
 197        let history_store =
 198            cx.new(|cx| HistoryStore::new(thread_store.clone(), context_store.clone(), cx));
 199
 200        let thread = cx.new(|cx| {
 201            ActiveThread::new(
 202                thread.clone(),
 203                thread_store.clone(),
 204                language_registry.clone(),
 205                message_editor_context_store.clone(),
 206                workspace.clone(),
 207                window,
 208                cx,
 209            )
 210        });
 211
 212        Self {
 213            active_view: ActiveView::Thread,
 214            workspace,
 215            project: project.clone(),
 216            fs: fs.clone(),
 217            language_registry,
 218            thread_store: thread_store.clone(),
 219            thread,
 220            message_editor,
 221            context_store,
 222            context_editor: None,
 223            configuration: None,
 224            configuration_subscription: None,
 225            local_timezone: UtcOffset::from_whole_seconds(
 226                chrono::Local::now().offset().local_minus_utc(),
 227            )
 228            .unwrap(),
 229            history_store: history_store.clone(),
 230            history: cx.new(|cx| ThreadHistory::new(weak_self, history_store, cx)),
 231            assistant_dropdown_menu_handle: PopoverMenuHandle::default(),
 232            width: None,
 233            height: None,
 234        }
 235    }
 236
 237    pub fn toggle_focus(
 238        workspace: &mut Workspace,
 239        _: &ToggleFocus,
 240        window: &mut Window,
 241        cx: &mut Context<Workspace>,
 242    ) {
 243        if workspace
 244            .panel::<Self>(cx)
 245            .is_some_and(|panel| panel.read(cx).enabled(cx))
 246        {
 247            workspace.toggle_panel_focus::<Self>(window, cx);
 248        }
 249    }
 250
 251    pub(crate) fn local_timezone(&self) -> UtcOffset {
 252        self.local_timezone
 253    }
 254
 255    pub(crate) fn thread_store(&self) -> &Entity<ThreadStore> {
 256        &self.thread_store
 257    }
 258
 259    fn cancel(
 260        &mut self,
 261        _: &editor::actions::Cancel,
 262        _window: &mut Window,
 263        cx: &mut Context<Self>,
 264    ) {
 265        self.thread
 266            .update(cx, |thread, cx| thread.cancel_last_completion(cx));
 267    }
 268
 269    fn new_thread(&mut self, action: &NewThread, window: &mut Window, cx: &mut Context<Self>) {
 270        let thread = self
 271            .thread_store
 272            .update(cx, |this, cx| this.create_thread(cx));
 273
 274        self.active_view = ActiveView::Thread;
 275
 276        let message_editor_context_store = cx.new(|_cx| {
 277            crate::context_store::ContextStore::new(
 278                self.workspace.clone(),
 279                Some(self.thread_store.downgrade()),
 280            )
 281        });
 282
 283        if let Some(other_thread_id) = action.from_thread_id.clone() {
 284            let other_thread_task = self
 285                .thread_store
 286                .update(cx, |this, cx| this.open_thread(&other_thread_id, cx));
 287
 288            cx.spawn({
 289                let context_store = message_editor_context_store.clone();
 290
 291                async move |_panel, cx| {
 292                    let other_thread = other_thread_task.await?;
 293
 294                    context_store.update(cx, |this, cx| {
 295                        this.add_thread(other_thread, false, cx);
 296                    })?;
 297                    anyhow::Ok(())
 298                }
 299            })
 300            .detach_and_log_err(cx);
 301        }
 302
 303        self.thread = cx.new(|cx| {
 304            ActiveThread::new(
 305                thread.clone(),
 306                self.thread_store.clone(),
 307                self.language_registry.clone(),
 308                message_editor_context_store.clone(),
 309                self.workspace.clone(),
 310                window,
 311                cx,
 312            )
 313        });
 314        self.message_editor = cx.new(|cx| {
 315            MessageEditor::new(
 316                self.fs.clone(),
 317                self.workspace.clone(),
 318                message_editor_context_store,
 319                self.thread_store.downgrade(),
 320                thread,
 321                window,
 322                cx,
 323            )
 324        });
 325        self.message_editor.focus_handle(cx).focus(window);
 326    }
 327
 328    fn new_prompt_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 329        self.active_view = ActiveView::PromptEditor;
 330
 331        let context = self
 332            .context_store
 333            .update(cx, |context_store, cx| context_store.create(cx));
 334        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
 335            .log_err()
 336            .flatten();
 337
 338        self.context_editor = Some(cx.new(|cx| {
 339            let mut editor = ContextEditor::for_context(
 340                context,
 341                self.fs.clone(),
 342                self.workspace.clone(),
 343                self.project.clone(),
 344                lsp_adapter_delegate,
 345                window,
 346                cx,
 347            );
 348            editor.insert_default_prompt(window, cx);
 349            editor
 350        }));
 351
 352        if let Some(context_editor) = self.context_editor.as_ref() {
 353            context_editor.focus_handle(cx).focus(window);
 354        }
 355    }
 356
 357    fn deploy_prompt_library(
 358        &mut self,
 359        _: &OpenPromptLibrary,
 360        _window: &mut Window,
 361        cx: &mut Context<Self>,
 362    ) {
 363        open_prompt_library(
 364            self.language_registry.clone(),
 365            Box::new(PromptLibraryInlineAssist::new(self.workspace.clone())),
 366            Arc::new(|| {
 367                Box::new(SlashCommandCompletionProvider::new(
 368                    Arc::new(SlashCommandWorkingSet::default()),
 369                    None,
 370                    None,
 371                ))
 372            }),
 373            cx,
 374        )
 375        .detach_and_log_err(cx);
 376    }
 377
 378    fn open_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 379        self.thread_store
 380            .update(cx, |thread_store, cx| thread_store.reload(cx))
 381            .detach_and_log_err(cx);
 382        self.active_view = ActiveView::History;
 383        self.history.focus_handle(cx).focus(window);
 384        cx.notify();
 385    }
 386
 387    pub(crate) fn open_saved_prompt_editor(
 388        &mut self,
 389        path: PathBuf,
 390        window: &mut Window,
 391        cx: &mut Context<Self>,
 392    ) -> Task<Result<()>> {
 393        let context = self
 394            .context_store
 395            .update(cx, |store, cx| store.open_local_context(path.clone(), cx));
 396        let fs = self.fs.clone();
 397        let project = self.project.clone();
 398        let workspace = self.workspace.clone();
 399
 400        let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();
 401
 402        cx.spawn_in(window, async move |this, cx| {
 403            let context = context.await?;
 404            this.update_in(cx, |this, window, cx| {
 405                let editor = cx.new(|cx| {
 406                    ContextEditor::for_context(
 407                        context,
 408                        fs,
 409                        workspace,
 410                        project,
 411                        lsp_adapter_delegate,
 412                        window,
 413                        cx,
 414                    )
 415                });
 416                this.active_view = ActiveView::PromptEditor;
 417                this.context_editor = Some(editor);
 418
 419                anyhow::Ok(())
 420            })??;
 421            Ok(())
 422        })
 423    }
 424
 425    pub(crate) fn open_thread(
 426        &mut self,
 427        thread_id: &ThreadId,
 428        window: &mut Window,
 429        cx: &mut Context<Self>,
 430    ) -> Task<Result<()>> {
 431        let open_thread_task = self
 432            .thread_store
 433            .update(cx, |this, cx| this.open_thread(thread_id, cx));
 434
 435        cx.spawn_in(window, async move |this, cx| {
 436            let thread = open_thread_task.await?;
 437            this.update_in(cx, |this, window, cx| {
 438                this.active_view = ActiveView::Thread;
 439                let message_editor_context_store = cx.new(|_cx| {
 440                    crate::context_store::ContextStore::new(
 441                        this.workspace.clone(),
 442                        Some(this.thread_store.downgrade()),
 443                    )
 444                });
 445                this.thread = cx.new(|cx| {
 446                    ActiveThread::new(
 447                        thread.clone(),
 448                        this.thread_store.clone(),
 449                        this.language_registry.clone(),
 450                        message_editor_context_store.clone(),
 451                        this.workspace.clone(),
 452                        window,
 453                        cx,
 454                    )
 455                });
 456                this.message_editor = cx.new(|cx| {
 457                    MessageEditor::new(
 458                        this.fs.clone(),
 459                        this.workspace.clone(),
 460                        message_editor_context_store,
 461                        this.thread_store.downgrade(),
 462                        thread,
 463                        window,
 464                        cx,
 465                    )
 466                });
 467                this.message_editor.focus_handle(cx).focus(window);
 468            })
 469        })
 470    }
 471
 472    pub fn open_agent_diff(
 473        &mut self,
 474        _: &OpenAgentDiff,
 475        window: &mut Window,
 476        cx: &mut Context<Self>,
 477    ) {
 478        let thread = self.thread.read(cx).thread().clone();
 479        AgentDiff::deploy(thread, self.workspace.clone(), window, cx).log_err();
 480    }
 481
 482    pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 483        let context_server_manager = self.thread_store.read(cx).context_server_manager();
 484        let tools = self.thread_store.read(cx).tools();
 485
 486        self.active_view = ActiveView::Configuration;
 487        self.configuration = Some(
 488            cx.new(|cx| AssistantConfiguration::new(context_server_manager, tools, window, cx)),
 489        );
 490
 491        if let Some(configuration) = self.configuration.as_ref() {
 492            self.configuration_subscription = Some(cx.subscribe_in(
 493                configuration,
 494                window,
 495                Self::handle_assistant_configuration_event,
 496            ));
 497
 498            configuration.focus_handle(cx).focus(window);
 499        }
 500    }
 501
 502    pub(crate) fn open_active_thread_as_markdown(
 503        &mut self,
 504        _: &OpenActiveThreadAsMarkdown,
 505        window: &mut Window,
 506        cx: &mut Context<Self>,
 507    ) {
 508        let Some(workspace) = self
 509            .workspace
 510            .upgrade()
 511            .ok_or_else(|| anyhow!("workspace dropped"))
 512            .log_err()
 513        else {
 514            return;
 515        };
 516
 517        let markdown_language_task = workspace
 518            .read(cx)
 519            .app_state()
 520            .languages
 521            .language_for_name("Markdown");
 522        let thread = self.active_thread(cx);
 523        cx.spawn_in(window, async move |_this, cx| {
 524            let markdown_language = markdown_language_task.await?;
 525
 526            workspace.update_in(cx, |workspace, window, cx| {
 527                let thread = thread.read(cx);
 528                let markdown = thread.to_markdown(cx)?;
 529                let thread_summary = thread
 530                    .summary()
 531                    .map(|summary| summary.to_string())
 532                    .unwrap_or_else(|| "Thread".to_string());
 533
 534                let project = workspace.project().clone();
 535                let buffer = project.update(cx, |project, cx| {
 536                    project.create_local_buffer(&markdown, Some(markdown_language), cx)
 537                });
 538                let buffer = cx.new(|cx| {
 539                    MultiBuffer::singleton(buffer, cx).with_title(thread_summary.clone())
 540                });
 541
 542                workspace.add_item_to_active_pane(
 543                    Box::new(cx.new(|cx| {
 544                        let mut editor =
 545                            Editor::for_multibuffer(buffer, Some(project.clone()), window, cx);
 546                        editor.set_breadcrumb_header(thread_summary);
 547                        editor
 548                    })),
 549                    None,
 550                    true,
 551                    window,
 552                    cx,
 553                );
 554
 555                anyhow::Ok(())
 556            })
 557        })
 558        .detach_and_log_err(cx);
 559    }
 560
 561    fn handle_assistant_configuration_event(
 562        &mut self,
 563        _entity: &Entity<AssistantConfiguration>,
 564        event: &AssistantConfigurationEvent,
 565        window: &mut Window,
 566        cx: &mut Context<Self>,
 567    ) {
 568        match event {
 569            AssistantConfigurationEvent::NewThread(provider) => {
 570                if LanguageModelRegistry::read_global(cx)
 571                    .active_provider()
 572                    .map_or(true, |active_provider| {
 573                        active_provider.id() != provider.id()
 574                    })
 575                {
 576                    if let Some(model) = provider.default_model(cx) {
 577                        update_settings_file::<AssistantSettings>(
 578                            self.fs.clone(),
 579                            cx,
 580                            move |settings, _| settings.set_model(model),
 581                        );
 582                    }
 583                }
 584
 585                self.new_thread(&NewThread::default(), window, cx);
 586            }
 587        }
 588    }
 589
 590    pub(crate) fn active_thread(&self, cx: &App) -> Entity<Thread> {
 591        self.thread.read(cx).thread().clone()
 592    }
 593
 594    pub(crate) fn delete_thread(&mut self, thread_id: &ThreadId, cx: &mut Context<Self>) {
 595        self.thread_store
 596            .update(cx, |this, cx| this.delete_thread(thread_id, cx))
 597            .detach_and_log_err(cx);
 598    }
 599
 600    pub(crate) fn active_context_editor(&self) -> Option<Entity<ContextEditor>> {
 601        self.context_editor.clone()
 602    }
 603
 604    pub(crate) fn delete_context(&mut self, path: PathBuf, cx: &mut Context<Self>) {
 605        self.context_store
 606            .update(cx, |this, cx| this.delete_local_context(path, cx))
 607            .detach_and_log_err(cx);
 608    }
 609}
 610
 611impl Focusable for AssistantPanel {
 612    fn focus_handle(&self, cx: &App) -> FocusHandle {
 613        match self.active_view {
 614            ActiveView::Thread => self.message_editor.focus_handle(cx),
 615            ActiveView::History => self.history.focus_handle(cx),
 616            ActiveView::PromptEditor => {
 617                if let Some(context_editor) = self.context_editor.as_ref() {
 618                    context_editor.focus_handle(cx)
 619                } else {
 620                    cx.focus_handle()
 621                }
 622            }
 623            ActiveView::Configuration => {
 624                if let Some(configuration) = self.configuration.as_ref() {
 625                    configuration.focus_handle(cx)
 626                } else {
 627                    cx.focus_handle()
 628                }
 629            }
 630        }
 631    }
 632}
 633
 634impl EventEmitter<PanelEvent> for AssistantPanel {}
 635
 636impl Panel for AssistantPanel {
 637    fn persistent_name() -> &'static str {
 638        "AgentPanel"
 639    }
 640
 641    fn position(&self, _window: &Window, cx: &App) -> DockPosition {
 642        match AssistantSettings::get_global(cx).dock {
 643            AssistantDockPosition::Left => DockPosition::Left,
 644            AssistantDockPosition::Bottom => DockPosition::Bottom,
 645            AssistantDockPosition::Right => DockPosition::Right,
 646        }
 647    }
 648
 649    fn position_is_valid(&self, _: DockPosition) -> bool {
 650        true
 651    }
 652
 653    fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
 654        settings::update_settings_file::<AssistantSettings>(
 655            self.fs.clone(),
 656            cx,
 657            move |settings, _| {
 658                let dock = match position {
 659                    DockPosition::Left => AssistantDockPosition::Left,
 660                    DockPosition::Bottom => AssistantDockPosition::Bottom,
 661                    DockPosition::Right => AssistantDockPosition::Right,
 662                };
 663                settings.set_dock(dock);
 664            },
 665        );
 666    }
 667
 668    fn size(&self, window: &Window, cx: &App) -> Pixels {
 669        let settings = AssistantSettings::get_global(cx);
 670        match self.position(window, cx) {
 671            DockPosition::Left | DockPosition::Right => {
 672                self.width.unwrap_or(settings.default_width)
 673            }
 674            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
 675        }
 676    }
 677
 678    fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
 679        match self.position(window, cx) {
 680            DockPosition::Left | DockPosition::Right => self.width = size,
 681            DockPosition::Bottom => self.height = size,
 682        }
 683        cx.notify();
 684    }
 685
 686    fn set_active(&mut self, _active: bool, _window: &mut Window, _cx: &mut Context<Self>) {}
 687
 688    fn remote_id() -> Option<proto::PanelId> {
 689        Some(proto::PanelId::AssistantPanel)
 690    }
 691
 692    fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
 693        (self.enabled(cx) && AssistantSettings::get_global(cx).button)
 694            .then_some(IconName::ZedAssistant)
 695    }
 696
 697    fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
 698        Some("Agent Panel")
 699    }
 700
 701    fn toggle_action(&self) -> Box<dyn Action> {
 702        Box::new(ToggleFocus)
 703    }
 704
 705    fn activation_priority(&self) -> u32 {
 706        3
 707    }
 708
 709    fn enabled(&self, cx: &App) -> bool {
 710        AssistantSettings::get_global(cx).enabled
 711    }
 712}
 713
 714impl AssistantPanel {
 715    fn render_toolbar(&self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 716        let thread = self.thread.read(cx);
 717        let is_empty = thread.is_empty();
 718
 719        let thread_id = thread.thread().read(cx).id().clone();
 720        let focus_handle = self.focus_handle(cx);
 721
 722        let title = match self.active_view {
 723            ActiveView::Thread => {
 724                if is_empty {
 725                    thread.summary_or_default(cx)
 726                } else {
 727                    thread
 728                        .summary(cx)
 729                        .unwrap_or_else(|| SharedString::from("Loading Summary…"))
 730                }
 731            }
 732            ActiveView::PromptEditor => self
 733                .context_editor
 734                .as_ref()
 735                .map(|context_editor| {
 736                    SharedString::from(context_editor.read(cx).title(cx).to_string())
 737                })
 738                .unwrap_or_else(|| SharedString::from("Loading Summary…")),
 739            ActiveView::History => "History".into(),
 740            ActiveView::Configuration => "Settings".into(),
 741        };
 742
 743        h_flex()
 744            .id("assistant-toolbar")
 745            .h(Tab::container_height(cx))
 746            .flex_none()
 747            .justify_between()
 748            .gap(DynamicSpacing::Base08.rems(cx))
 749            .bg(cx.theme().colors().tab_bar_background)
 750            .border_b_1()
 751            .border_color(cx.theme().colors().border)
 752            .child(
 753                div()
 754                    .id("title")
 755                    .overflow_x_scroll()
 756                    .px(DynamicSpacing::Base08.rems(cx))
 757                    .child(Label::new(title).truncate()),
 758            )
 759            .child(
 760                h_flex()
 761                    .h_full()
 762                    .pl_2()
 763                    .gap_2()
 764                    .bg(cx.theme().colors().tab_bar_background)
 765                    .children(if matches!(self.active_view, ActiveView::PromptEditor) {
 766                        self.context_editor
 767                            .as_ref()
 768                            .and_then(|editor| render_remaining_tokens(editor, cx))
 769                    } else {
 770                        None
 771                    })
 772                    .child(
 773                        h_flex()
 774                            .h_full()
 775                            .px(DynamicSpacing::Base08.rems(cx))
 776                            .border_l_1()
 777                            .border_color(cx.theme().colors().border)
 778                            .gap(DynamicSpacing::Base02.rems(cx))
 779                            .child(
 780                                IconButton::new("new", IconName::Plus)
 781                                    .icon_size(IconSize::Small)
 782                                    .style(ButtonStyle::Subtle)
 783                                    .tooltip(move |window, cx| {
 784                                        Tooltip::for_action_in(
 785                                            "New Thread",
 786                                            &NewThread::default(),
 787                                            &focus_handle,
 788                                            window,
 789                                            cx,
 790                                        )
 791                                    })
 792                                    .on_click(move |_event, window, cx| {
 793                                        window.dispatch_action(
 794                                            NewThread::default().boxed_clone(),
 795                                            cx,
 796                                        );
 797                                    }),
 798                            )
 799                            .child(
 800                                PopoverMenu::new("assistant-menu")
 801                                    .trigger_with_tooltip(
 802                                        IconButton::new("new", IconName::Ellipsis)
 803                                            .icon_size(IconSize::Small)
 804                                            .style(ButtonStyle::Subtle),
 805                                        Tooltip::text("Toggle Agent Menu"),
 806                                    )
 807                                    .anchor(Corner::TopRight)
 808                                    .with_handle(self.assistant_dropdown_menu_handle.clone())
 809                                    .menu(move |window, cx| {
 810                                        Some(ContextMenu::build(
 811                                            window,
 812                                            cx,
 813                                            |menu, _window, _cx| {
 814                                                menu.action(
 815                                                    "New Thread",
 816                                                    Box::new(NewThread {
 817                                                        from_thread_id: None,
 818                                                    }),
 819                                                )
 820                                                .action(
 821                                                    "New Prompt Editor",
 822                                                    NewPromptEditor.boxed_clone(),
 823                                                )
 824                                                .when(!is_empty, |menu| {
 825                                                    menu.action(
 826                                                        "Continue in New Thread",
 827                                                        Box::new(NewThread {
 828                                                            from_thread_id: Some(thread_id.clone()),
 829                                                        }),
 830                                                    )
 831                                                })
 832                                                .separator()
 833                                                .action("History", OpenHistory.boxed_clone())
 834                                                .action("Settings", OpenConfiguration.boxed_clone())
 835                                            },
 836                                        ))
 837                                    }),
 838                            ),
 839                    ),
 840            )
 841    }
 842
 843    fn render_active_thread_or_empty_state(
 844        &self,
 845        window: &mut Window,
 846        cx: &mut Context<Self>,
 847    ) -> AnyElement {
 848        if self.thread.read(cx).is_empty() {
 849            return self
 850                .render_thread_empty_state(window, cx)
 851                .into_any_element();
 852        }
 853
 854        self.thread.clone().into_any_element()
 855    }
 856
 857    fn configuration_error(&self, cx: &App) -> Option<ConfigurationError> {
 858        let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
 859            return Some(ConfigurationError::NoProvider);
 860        };
 861
 862        if !provider.is_authenticated(cx) {
 863            return Some(ConfigurationError::ProviderNotAuthenticated);
 864        }
 865
 866        if provider.must_accept_terms(cx) {
 867            return Some(ConfigurationError::ProviderPendingTermsAcceptance(provider));
 868        }
 869
 870        None
 871    }
 872
 873    fn render_thread_empty_state(
 874        &self,
 875        window: &mut Window,
 876        cx: &mut Context<Self>,
 877    ) -> impl IntoElement {
 878        let recent_history = self
 879            .history_store
 880            .update(cx, |this, cx| this.recent_entries(6, cx));
 881
 882        let configuration_error = self.configuration_error(cx);
 883        let no_error = configuration_error.is_none();
 884        let focus_handle = self.focus_handle(cx);
 885
 886        v_flex()
 887            .size_full()
 888            .when(recent_history.is_empty(), |this| {
 889                let configuration_error_ref = &configuration_error;
 890                this.child(
 891                    v_flex()
 892                        .size_full()
 893                        .max_w_80()
 894                        .mx_auto()
 895                        .justify_center()
 896                        .items_center()
 897                        .gap_1()
 898                        .child(
 899                            h_flex().child(
 900                                Headline::new("Welcome to the Agent Panel")
 901                            ),
 902                        )
 903                        .when(no_error, |parent| {
 904                            parent
 905                                .child(
 906                                    h_flex().child(
 907                                        Label::new("Ask and build anything.")
 908                                            .color(Color::Muted)
 909                                            .mb_2p5(),
 910                                    ),
 911                                )
 912                                .child(
 913                                    Button::new("new-thread", "Start New Thread")
 914                                        .icon(IconName::Plus)
 915                                        .icon_position(IconPosition::Start)
 916                                        .icon_size(IconSize::Small)
 917                                        .icon_color(Color::Muted)
 918                                        .full_width()
 919                                        .key_binding(KeyBinding::for_action_in(
 920                                            &NewThread::default(),
 921                                            &focus_handle,
 922                                            window,
 923                                            cx,
 924                                        ))
 925                                        .on_click(|_event, window, cx| {
 926                                            window.dispatch_action(NewThread::default().boxed_clone(), cx)
 927                                        }),
 928                                )
 929                                .child(
 930                                    Button::new("context", "Add Context")
 931                                        .icon(IconName::FileCode)
 932                                        .icon_position(IconPosition::Start)
 933                                        .icon_size(IconSize::Small)
 934                                        .icon_color(Color::Muted)
 935                                        .full_width()
 936                                        .key_binding(KeyBinding::for_action_in(
 937                                            &ToggleContextPicker,
 938                                            &focus_handle,
 939                                            window,
 940                                            cx,
 941                                        ))
 942                                        .on_click(|_event, window, cx| {
 943                                            window.dispatch_action(ToggleContextPicker.boxed_clone(), cx)
 944                                        }),
 945                                )
 946                                .child(
 947                                    Button::new("mode", "Switch Model")
 948                                        .icon(IconName::DatabaseZap)
 949                                        .icon_position(IconPosition::Start)
 950                                        .icon_size(IconSize::Small)
 951                                        .icon_color(Color::Muted)
 952                                        .full_width()
 953                                        .key_binding(KeyBinding::for_action_in(
 954                                            &ToggleModelSelector,
 955                                            &focus_handle,
 956                                            window,
 957                                            cx,
 958                                        ))
 959                                        .on_click(|_event, window, cx| {
 960                                            window.dispatch_action(ToggleModelSelector.boxed_clone(), cx)
 961                                        }),
 962                                )
 963                                .child(
 964                                    Button::new("settings", "View Settings")
 965                                        .icon(IconName::Settings)
 966                                        .icon_position(IconPosition::Start)
 967                                        .icon_size(IconSize::Small)
 968                                        .icon_color(Color::Muted)
 969                                        .full_width()
 970                                        .key_binding(KeyBinding::for_action_in(
 971                                            &OpenConfiguration,
 972                                            &focus_handle,
 973                                            window,
 974                                            cx,
 975                                        ))
 976                                        .on_click(|_event, window, cx| {
 977                                            window.dispatch_action(OpenConfiguration.boxed_clone(), cx)
 978                                        }),
 979                                )
 980                        })
 981                        .map(|parent| {
 982                            match configuration_error_ref {
 983                                Some(ConfigurationError::ProviderNotAuthenticated)
 984                                | Some(ConfigurationError::NoProvider) => {
 985                                    parent
 986                                        .child(
 987                                            h_flex().child(
 988                                                Label::new("To start using the agent, configure at least one LLM provider.")
 989                                                    .color(Color::Muted)
 990                                                    .mb_2p5()
 991                                            )
 992                                        )
 993                                        .child(
 994                                            Button::new("settings", "Configure a Provider")
 995                                                .icon(IconName::Settings)
 996                                                .icon_position(IconPosition::Start)
 997                                                .icon_size(IconSize::Small)
 998                                                .icon_color(Color::Muted)
 999                                                .full_width()
1000                                                .key_binding(KeyBinding::for_action_in(
1001                                                    &OpenConfiguration,
1002                                                    &focus_handle,
1003                                                    window,
1004                                                    cx,
1005                                                ))
1006                                                .on_click(|_event, window, cx| {
1007                                                    window.dispatch_action(OpenConfiguration.boxed_clone(), cx)
1008                                                }),
1009                                        )
1010                                }
1011                                Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
1012                                    parent.children(
1013                                        provider.render_accept_terms(
1014                                            LanguageModelProviderTosView::ThreadFreshStart,
1015                                            cx,
1016                                        ),
1017                                    )
1018                                }
1019                                None => parent,
1020                            }
1021                        })
1022                )
1023            })
1024            .when(!recent_history.is_empty(), |parent| {
1025                let focus_handle = focus_handle.clone();
1026                let configuration_error_ref = &configuration_error;
1027
1028                parent
1029                    .p_1p5()
1030                    .justify_end()
1031                    .gap_1()
1032                    .child(
1033                        h_flex()
1034                            .pl_1p5()
1035                            .pb_1()
1036                            .w_full()
1037                            .justify_between()
1038                            .border_b_1()
1039                            .border_color(cx.theme().colors().border_variant)
1040                            .child(
1041                                Label::new("Past Interactions")
1042                                    .size(LabelSize::Small)
1043                                    .color(Color::Muted),
1044                            )
1045                            .child(
1046                                Button::new("view-history", "View All")
1047                                    .style(ButtonStyle::Subtle)
1048                                    .label_size(LabelSize::Small)
1049                                    .key_binding(
1050                                        KeyBinding::for_action_in(
1051                                            &OpenHistory,
1052                                            &self.focus_handle(cx),
1053                                            window,
1054                                            cx,
1055                                        ).map(|kb| kb.size(rems_from_px(12.))),
1056                                    )
1057                                    .on_click(move |_event, window, cx| {
1058                                        window.dispatch_action(OpenHistory.boxed_clone(), cx);
1059                                    }),
1060                            ),
1061                    )
1062                    .child(
1063                        v_flex()
1064                            .gap_1()
1065                            .children(
1066                                recent_history.into_iter().map(|entry| {
1067                                    // TODO: Add keyboard navigation.
1068                                    match entry {
1069                                        HistoryEntry::Thread(thread) => {
1070                                            PastThread::new(thread, cx.entity().downgrade(), false)
1071                                                .into_any_element()
1072                                        }
1073                                        HistoryEntry::Context(context) => {
1074                                            PastContext::new(context, cx.entity().downgrade(), false)
1075                                                .into_any_element()
1076                                        }
1077                                    }
1078                                }),
1079                            )
1080                    )
1081                    .map(|parent| {
1082                        match configuration_error_ref {
1083                            Some(ConfigurationError::ProviderNotAuthenticated)
1084                            | Some(ConfigurationError::NoProvider) => {
1085                                parent
1086                                    .child(
1087                                        Banner::new()
1088                                            .severity(ui::Severity::Warning)
1089                                            .children(
1090                                                Label::new(
1091                                                    "Configure at least one LLM provider to start using the panel.",
1092                                                )
1093                                                .size(LabelSize::Small),
1094                                            )
1095                                            .action_slot(
1096                                                Button::new("settings", "Configure Provider")
1097                                                    .style(ButtonStyle::Tinted(ui::TintColor::Warning))
1098                                                    .label_size(LabelSize::Small)
1099                                                    .key_binding(
1100                                                        KeyBinding::for_action_in(
1101                                                            &OpenConfiguration,
1102                                                            &focus_handle,
1103                                                            window,
1104                                                            cx,
1105                                                        )
1106                                                        .map(|kb| kb.size(rems_from_px(12.))),
1107                                                    )
1108                                                    .on_click(|_event, window, cx| {
1109                                                        window.dispatch_action(
1110                                                            OpenConfiguration.boxed_clone(),
1111                                                            cx,
1112                                                        )
1113                                                    }),
1114                                            ),
1115                                    )
1116                            }
1117                            Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
1118                                parent
1119                                    .child(
1120                                        Banner::new()
1121                                            .severity(ui::Severity::Warning)
1122                                            .children(
1123                                                h_flex()
1124                                                    .w_full()
1125                                                    .children(
1126                                                        provider.render_accept_terms(
1127                                                            LanguageModelProviderTosView::ThreadtEmptyState,
1128                                                            cx,
1129                                                        ),
1130                                                    ),
1131                                            ),
1132                                    )
1133                            }
1134                            None => parent,
1135                        }
1136                    })
1137            })
1138    }
1139
1140    fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
1141        let last_error = self.thread.read(cx).last_error()?;
1142
1143        Some(
1144            div()
1145                .absolute()
1146                .right_3()
1147                .bottom_12()
1148                .max_w_96()
1149                .py_2()
1150                .px_3()
1151                .elevation_2(cx)
1152                .occlude()
1153                .child(match last_error {
1154                    ThreadError::PaymentRequired => self.render_payment_required_error(cx),
1155                    ThreadError::MaxMonthlySpendReached => {
1156                        self.render_max_monthly_spend_reached_error(cx)
1157                    }
1158                    ThreadError::Message { header, message } => {
1159                        self.render_error_message(header, message, cx)
1160                    }
1161                })
1162                .into_any(),
1163        )
1164    }
1165
1166    fn render_payment_required_error(&self, cx: &mut Context<Self>) -> AnyElement {
1167        const ERROR_MESSAGE: &str = "Free tier exceeded. Subscribe and add payment to continue using Zed LLMs. You'll be billed at cost for tokens used.";
1168
1169        v_flex()
1170            .gap_0p5()
1171            .child(
1172                h_flex()
1173                    .gap_1p5()
1174                    .items_center()
1175                    .child(Icon::new(IconName::XCircle).color(Color::Error))
1176                    .child(Label::new("Free Usage Exceeded").weight(FontWeight::MEDIUM)),
1177            )
1178            .child(
1179                div()
1180                    .id("error-message")
1181                    .max_h_24()
1182                    .overflow_y_scroll()
1183                    .child(Label::new(ERROR_MESSAGE)),
1184            )
1185            .child(
1186                h_flex()
1187                    .justify_end()
1188                    .mt_1()
1189                    .child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
1190                        |this, _, _, cx| {
1191                            this.thread.update(cx, |this, _cx| {
1192                                this.clear_last_error();
1193                            });
1194
1195                            cx.open_url(&zed_urls::account_url(cx));
1196                            cx.notify();
1197                        },
1198                    )))
1199                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
1200                        |this, _, _, cx| {
1201                            this.thread.update(cx, |this, _cx| {
1202                                this.clear_last_error();
1203                            });
1204
1205                            cx.notify();
1206                        },
1207                    ))),
1208            )
1209            .into_any()
1210    }
1211
1212    fn render_max_monthly_spend_reached_error(&self, cx: &mut Context<Self>) -> AnyElement {
1213        const ERROR_MESSAGE: &str = "You have reached your maximum monthly spend. Increase your spend limit to continue using Zed LLMs.";
1214
1215        v_flex()
1216            .gap_0p5()
1217            .child(
1218                h_flex()
1219                    .gap_1p5()
1220                    .items_center()
1221                    .child(Icon::new(IconName::XCircle).color(Color::Error))
1222                    .child(Label::new("Max Monthly Spend Reached").weight(FontWeight::MEDIUM)),
1223            )
1224            .child(
1225                div()
1226                    .id("error-message")
1227                    .max_h_24()
1228                    .overflow_y_scroll()
1229                    .child(Label::new(ERROR_MESSAGE)),
1230            )
1231            .child(
1232                h_flex()
1233                    .justify_end()
1234                    .mt_1()
1235                    .child(
1236                        Button::new("subscribe", "Update Monthly Spend Limit").on_click(
1237                            cx.listener(|this, _, _, cx| {
1238                                this.thread.update(cx, |this, _cx| {
1239                                    this.clear_last_error();
1240                                });
1241
1242                                cx.open_url(&zed_urls::account_url(cx));
1243                                cx.notify();
1244                            }),
1245                        ),
1246                    )
1247                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
1248                        |this, _, _, cx| {
1249                            this.thread.update(cx, |this, _cx| {
1250                                this.clear_last_error();
1251                            });
1252
1253                            cx.notify();
1254                        },
1255                    ))),
1256            )
1257            .into_any()
1258    }
1259
1260    fn render_error_message(
1261        &self,
1262        header: SharedString,
1263        message: SharedString,
1264        cx: &mut Context<Self>,
1265    ) -> AnyElement {
1266        v_flex()
1267            .gap_0p5()
1268            .child(
1269                h_flex()
1270                    .gap_1p5()
1271                    .items_center()
1272                    .child(Icon::new(IconName::XCircle).color(Color::Error))
1273                    .child(Label::new(header).weight(FontWeight::MEDIUM)),
1274            )
1275            .child(
1276                div()
1277                    .id("error-message")
1278                    .max_h_32()
1279                    .overflow_y_scroll()
1280                    .child(Label::new(message)),
1281            )
1282            .child(
1283                h_flex()
1284                    .justify_end()
1285                    .mt_1()
1286                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
1287                        |this, _, _, cx| {
1288                            this.thread.update(cx, |this, _cx| {
1289                                this.clear_last_error();
1290                            });
1291
1292                            cx.notify();
1293                        },
1294                    ))),
1295            )
1296            .into_any()
1297    }
1298
1299    fn key_context(&self) -> KeyContext {
1300        let mut key_context = KeyContext::new_with_defaults();
1301        key_context.add("AgentPanel");
1302        if matches!(self.active_view, ActiveView::PromptEditor) {
1303            key_context.add("prompt_editor");
1304        }
1305        key_context
1306    }
1307}
1308
1309impl Render for AssistantPanel {
1310    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1311        v_flex()
1312            .key_context(self.key_context())
1313            .justify_between()
1314            .size_full()
1315            .on_action(cx.listener(Self::cancel))
1316            .on_action(cx.listener(|this, action: &NewThread, window, cx| {
1317                this.new_thread(action, window, cx);
1318            }))
1319            .on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
1320                this.open_history(window, cx);
1321            }))
1322            .on_action(cx.listener(|this, _: &OpenConfiguration, window, cx| {
1323                this.open_configuration(window, cx);
1324            }))
1325            .on_action(cx.listener(Self::open_active_thread_as_markdown))
1326            .on_action(cx.listener(Self::deploy_prompt_library))
1327            .on_action(cx.listener(Self::open_agent_diff))
1328            .child(self.render_toolbar(window, cx))
1329            .map(|parent| match self.active_view {
1330                ActiveView::Thread => parent
1331                    .child(self.render_active_thread_or_empty_state(window, cx))
1332                    .child(h_flex().child(self.message_editor.clone()))
1333                    .children(self.render_last_error(cx)),
1334                ActiveView::History => parent.child(self.history.clone()),
1335                ActiveView::PromptEditor => parent.children(self.context_editor.clone()),
1336                ActiveView::Configuration => parent.children(self.configuration.clone()),
1337            })
1338    }
1339}
1340
1341struct PromptLibraryInlineAssist {
1342    workspace: WeakEntity<Workspace>,
1343}
1344
1345impl PromptLibraryInlineAssist {
1346    pub fn new(workspace: WeakEntity<Workspace>) -> Self {
1347        Self { workspace }
1348    }
1349}
1350
1351impl prompt_library::InlineAssistDelegate for PromptLibraryInlineAssist {
1352    fn assist(
1353        &self,
1354        prompt_editor: &Entity<Editor>,
1355        _initial_prompt: Option<String>,
1356        window: &mut Window,
1357        cx: &mut Context<PromptLibrary>,
1358    ) {
1359        InlineAssistant::update_global(cx, |assistant, cx| {
1360            assistant.assist(&prompt_editor, self.workspace.clone(), None, window, cx)
1361        })
1362    }
1363
1364    fn focus_assistant_panel(
1365        &self,
1366        workspace: &mut Workspace,
1367        window: &mut Window,
1368        cx: &mut Context<Workspace>,
1369    ) -> bool {
1370        workspace
1371            .focus_panel::<AssistantPanel>(window, cx)
1372            .is_some()
1373    }
1374}
1375
1376pub struct ConcreteAssistantPanelDelegate;
1377
1378impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
1379    fn active_context_editor(
1380        &self,
1381        workspace: &mut Workspace,
1382        _window: &mut Window,
1383        cx: &mut Context<Workspace>,
1384    ) -> Option<Entity<ContextEditor>> {
1385        let panel = workspace.panel::<AssistantPanel>(cx)?;
1386        panel.update(cx, |panel, _cx| panel.context_editor.clone())
1387    }
1388
1389    fn open_saved_context(
1390        &self,
1391        workspace: &mut Workspace,
1392        path: std::path::PathBuf,
1393        window: &mut Window,
1394        cx: &mut Context<Workspace>,
1395    ) -> Task<Result<()>> {
1396        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
1397            return Task::ready(Err(anyhow!("Agent panel not found")));
1398        };
1399
1400        panel.update(cx, |panel, cx| {
1401            panel.open_saved_prompt_editor(path, window, cx)
1402        })
1403    }
1404
1405    fn open_remote_context(
1406        &self,
1407        _workspace: &mut Workspace,
1408        _context_id: assistant_context_editor::ContextId,
1409        _window: &mut Window,
1410        _cx: &mut Context<Workspace>,
1411    ) -> Task<Result<Entity<ContextEditor>>> {
1412        Task::ready(Err(anyhow!("opening remote context not implemented")))
1413    }
1414
1415    fn quote_selection(
1416        &self,
1417        _workspace: &mut Workspace,
1418        _creases: Vec<(String, String)>,
1419        _window: &mut Window,
1420        _cx: &mut Context<Workspace>,
1421    ) {
1422    }
1423}