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        let fs = self.fs.clone();
 486
 487        self.active_view = ActiveView::Configuration;
 488        self.configuration =
 489            Some(cx.new(|cx| {
 490                AssistantConfiguration::new(fs, context_server_manager, tools, window, cx)
 491            }));
 492
 493        if let Some(configuration) = self.configuration.as_ref() {
 494            self.configuration_subscription = Some(cx.subscribe_in(
 495                configuration,
 496                window,
 497                Self::handle_assistant_configuration_event,
 498            ));
 499
 500            configuration.focus_handle(cx).focus(window);
 501        }
 502    }
 503
 504    pub(crate) fn open_active_thread_as_markdown(
 505        &mut self,
 506        _: &OpenActiveThreadAsMarkdown,
 507        window: &mut Window,
 508        cx: &mut Context<Self>,
 509    ) {
 510        let Some(workspace) = self
 511            .workspace
 512            .upgrade()
 513            .ok_or_else(|| anyhow!("workspace dropped"))
 514            .log_err()
 515        else {
 516            return;
 517        };
 518
 519        let markdown_language_task = workspace
 520            .read(cx)
 521            .app_state()
 522            .languages
 523            .language_for_name("Markdown");
 524        let thread = self.active_thread(cx);
 525        cx.spawn_in(window, async move |_this, cx| {
 526            let markdown_language = markdown_language_task.await?;
 527
 528            workspace.update_in(cx, |workspace, window, cx| {
 529                let thread = thread.read(cx);
 530                let markdown = thread.to_markdown(cx)?;
 531                let thread_summary = thread
 532                    .summary()
 533                    .map(|summary| summary.to_string())
 534                    .unwrap_or_else(|| "Thread".to_string());
 535
 536                let project = workspace.project().clone();
 537                let buffer = project.update(cx, |project, cx| {
 538                    project.create_local_buffer(&markdown, Some(markdown_language), cx)
 539                });
 540                let buffer = cx.new(|cx| {
 541                    MultiBuffer::singleton(buffer, cx).with_title(thread_summary.clone())
 542                });
 543
 544                workspace.add_item_to_active_pane(
 545                    Box::new(cx.new(|cx| {
 546                        let mut editor =
 547                            Editor::for_multibuffer(buffer, Some(project.clone()), window, cx);
 548                        editor.set_breadcrumb_header(thread_summary);
 549                        editor
 550                    })),
 551                    None,
 552                    true,
 553                    window,
 554                    cx,
 555                );
 556
 557                anyhow::Ok(())
 558            })
 559        })
 560        .detach_and_log_err(cx);
 561    }
 562
 563    fn handle_assistant_configuration_event(
 564        &mut self,
 565        _entity: &Entity<AssistantConfiguration>,
 566        event: &AssistantConfigurationEvent,
 567        window: &mut Window,
 568        cx: &mut Context<Self>,
 569    ) {
 570        match event {
 571            AssistantConfigurationEvent::NewThread(provider) => {
 572                if LanguageModelRegistry::read_global(cx)
 573                    .active_provider()
 574                    .map_or(true, |active_provider| {
 575                        active_provider.id() != provider.id()
 576                    })
 577                {
 578                    if let Some(model) = provider.default_model(cx) {
 579                        update_settings_file::<AssistantSettings>(
 580                            self.fs.clone(),
 581                            cx,
 582                            move |settings, _| settings.set_model(model),
 583                        );
 584                    }
 585                }
 586
 587                self.new_thread(&NewThread::default(), window, cx);
 588            }
 589        }
 590    }
 591
 592    pub(crate) fn active_thread(&self, cx: &App) -> Entity<Thread> {
 593        self.thread.read(cx).thread().clone()
 594    }
 595
 596    pub(crate) fn delete_thread(&mut self, thread_id: &ThreadId, cx: &mut Context<Self>) {
 597        self.thread_store
 598            .update(cx, |this, cx| this.delete_thread(thread_id, cx))
 599            .detach_and_log_err(cx);
 600    }
 601
 602    pub(crate) fn active_context_editor(&self) -> Option<Entity<ContextEditor>> {
 603        self.context_editor.clone()
 604    }
 605
 606    pub(crate) fn delete_context(&mut self, path: PathBuf, cx: &mut Context<Self>) {
 607        self.context_store
 608            .update(cx, |this, cx| this.delete_local_context(path, cx))
 609            .detach_and_log_err(cx);
 610    }
 611}
 612
 613impl Focusable for AssistantPanel {
 614    fn focus_handle(&self, cx: &App) -> FocusHandle {
 615        match self.active_view {
 616            ActiveView::Thread => self.message_editor.focus_handle(cx),
 617            ActiveView::History => self.history.focus_handle(cx),
 618            ActiveView::PromptEditor => {
 619                if let Some(context_editor) = self.context_editor.as_ref() {
 620                    context_editor.focus_handle(cx)
 621                } else {
 622                    cx.focus_handle()
 623                }
 624            }
 625            ActiveView::Configuration => {
 626                if let Some(configuration) = self.configuration.as_ref() {
 627                    configuration.focus_handle(cx)
 628                } else {
 629                    cx.focus_handle()
 630                }
 631            }
 632        }
 633    }
 634}
 635
 636impl EventEmitter<PanelEvent> for AssistantPanel {}
 637
 638impl Panel for AssistantPanel {
 639    fn persistent_name() -> &'static str {
 640        "AgentPanel"
 641    }
 642
 643    fn position(&self, _window: &Window, cx: &App) -> DockPosition {
 644        match AssistantSettings::get_global(cx).dock {
 645            AssistantDockPosition::Left => DockPosition::Left,
 646            AssistantDockPosition::Bottom => DockPosition::Bottom,
 647            AssistantDockPosition::Right => DockPosition::Right,
 648        }
 649    }
 650
 651    fn position_is_valid(&self, _: DockPosition) -> bool {
 652        true
 653    }
 654
 655    fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
 656        settings::update_settings_file::<AssistantSettings>(
 657            self.fs.clone(),
 658            cx,
 659            move |settings, _| {
 660                let dock = match position {
 661                    DockPosition::Left => AssistantDockPosition::Left,
 662                    DockPosition::Bottom => AssistantDockPosition::Bottom,
 663                    DockPosition::Right => AssistantDockPosition::Right,
 664                };
 665                settings.set_dock(dock);
 666            },
 667        );
 668    }
 669
 670    fn size(&self, window: &Window, cx: &App) -> Pixels {
 671        let settings = AssistantSettings::get_global(cx);
 672        match self.position(window, cx) {
 673            DockPosition::Left | DockPosition::Right => {
 674                self.width.unwrap_or(settings.default_width)
 675            }
 676            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
 677        }
 678    }
 679
 680    fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
 681        match self.position(window, cx) {
 682            DockPosition::Left | DockPosition::Right => self.width = size,
 683            DockPosition::Bottom => self.height = size,
 684        }
 685        cx.notify();
 686    }
 687
 688    fn set_active(&mut self, _active: bool, _window: &mut Window, _cx: &mut Context<Self>) {}
 689
 690    fn remote_id() -> Option<proto::PanelId> {
 691        Some(proto::PanelId::AssistantPanel)
 692    }
 693
 694    fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
 695        (self.enabled(cx) && AssistantSettings::get_global(cx).button)
 696            .then_some(IconName::ZedAssistant)
 697    }
 698
 699    fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
 700        Some("Agent Panel")
 701    }
 702
 703    fn toggle_action(&self) -> Box<dyn Action> {
 704        Box::new(ToggleFocus)
 705    }
 706
 707    fn activation_priority(&self) -> u32 {
 708        3
 709    }
 710
 711    fn enabled(&self, cx: &App) -> bool {
 712        AssistantSettings::get_global(cx).enabled
 713    }
 714}
 715
 716impl AssistantPanel {
 717    fn render_toolbar(&self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 718        let thread = self.thread.read(cx);
 719        let is_empty = thread.is_empty();
 720
 721        let thread_id = thread.thread().read(cx).id().clone();
 722        let focus_handle = self.focus_handle(cx);
 723
 724        let title = match self.active_view {
 725            ActiveView::Thread => {
 726                if is_empty {
 727                    thread.summary_or_default(cx)
 728                } else {
 729                    thread
 730                        .summary(cx)
 731                        .unwrap_or_else(|| SharedString::from("Loading Summary…"))
 732                }
 733            }
 734            ActiveView::PromptEditor => self
 735                .context_editor
 736                .as_ref()
 737                .map(|context_editor| {
 738                    SharedString::from(context_editor.read(cx).title(cx).to_string())
 739                })
 740                .unwrap_or_else(|| SharedString::from("Loading Summary…")),
 741            ActiveView::History => "History".into(),
 742            ActiveView::Configuration => "Settings".into(),
 743        };
 744
 745        h_flex()
 746            .id("assistant-toolbar")
 747            .h(Tab::container_height(cx))
 748            .flex_none()
 749            .justify_between()
 750            .gap(DynamicSpacing::Base08.rems(cx))
 751            .bg(cx.theme().colors().tab_bar_background)
 752            .border_b_1()
 753            .border_color(cx.theme().colors().border)
 754            .child(
 755                div()
 756                    .id("title")
 757                    .overflow_x_scroll()
 758                    .px(DynamicSpacing::Base08.rems(cx))
 759                    .child(Label::new(title).truncate()),
 760            )
 761            .child(
 762                h_flex()
 763                    .h_full()
 764                    .pl_2()
 765                    .gap_2()
 766                    .bg(cx.theme().colors().tab_bar_background)
 767                    .children(if matches!(self.active_view, ActiveView::PromptEditor) {
 768                        self.context_editor
 769                            .as_ref()
 770                            .and_then(|editor| render_remaining_tokens(editor, cx))
 771                    } else {
 772                        None
 773                    })
 774                    .child(
 775                        h_flex()
 776                            .h_full()
 777                            .px(DynamicSpacing::Base08.rems(cx))
 778                            .border_l_1()
 779                            .border_color(cx.theme().colors().border)
 780                            .gap(DynamicSpacing::Base02.rems(cx))
 781                            .child(
 782                                IconButton::new("new", IconName::Plus)
 783                                    .icon_size(IconSize::Small)
 784                                    .style(ButtonStyle::Subtle)
 785                                    .tooltip(move |window, cx| {
 786                                        Tooltip::for_action_in(
 787                                            "New Thread",
 788                                            &NewThread::default(),
 789                                            &focus_handle,
 790                                            window,
 791                                            cx,
 792                                        )
 793                                    })
 794                                    .on_click(move |_event, window, cx| {
 795                                        window.dispatch_action(
 796                                            NewThread::default().boxed_clone(),
 797                                            cx,
 798                                        );
 799                                    }),
 800                            )
 801                            .child(
 802                                PopoverMenu::new("assistant-menu")
 803                                    .trigger_with_tooltip(
 804                                        IconButton::new("new", IconName::Ellipsis)
 805                                            .icon_size(IconSize::Small)
 806                                            .style(ButtonStyle::Subtle),
 807                                        Tooltip::text("Toggle Agent Menu"),
 808                                    )
 809                                    .anchor(Corner::TopRight)
 810                                    .with_handle(self.assistant_dropdown_menu_handle.clone())
 811                                    .menu(move |window, cx| {
 812                                        Some(ContextMenu::build(
 813                                            window,
 814                                            cx,
 815                                            |menu, _window, _cx| {
 816                                                menu.action(
 817                                                    "New Thread",
 818                                                    Box::new(NewThread {
 819                                                        from_thread_id: None,
 820                                                    }),
 821                                                )
 822                                                .action(
 823                                                    "New Prompt Editor",
 824                                                    NewPromptEditor.boxed_clone(),
 825                                                )
 826                                                .when(!is_empty, |menu| {
 827                                                    menu.action(
 828                                                        "Continue in New Thread",
 829                                                        Box::new(NewThread {
 830                                                            from_thread_id: Some(thread_id.clone()),
 831                                                        }),
 832                                                    )
 833                                                })
 834                                                .separator()
 835                                                .action("History", OpenHistory.boxed_clone())
 836                                                .action("Settings", OpenConfiguration.boxed_clone())
 837                                            },
 838                                        ))
 839                                    }),
 840                            ),
 841                    ),
 842            )
 843    }
 844
 845    fn render_active_thread_or_empty_state(
 846        &self,
 847        window: &mut Window,
 848        cx: &mut Context<Self>,
 849    ) -> AnyElement {
 850        if self.thread.read(cx).is_empty() {
 851            return self
 852                .render_thread_empty_state(window, cx)
 853                .into_any_element();
 854        }
 855
 856        self.thread.clone().into_any_element()
 857    }
 858
 859    fn configuration_error(&self, cx: &App) -> Option<ConfigurationError> {
 860        let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
 861            return Some(ConfigurationError::NoProvider);
 862        };
 863
 864        if !provider.is_authenticated(cx) {
 865            return Some(ConfigurationError::ProviderNotAuthenticated);
 866        }
 867
 868        if provider.must_accept_terms(cx) {
 869            return Some(ConfigurationError::ProviderPendingTermsAcceptance(provider));
 870        }
 871
 872        None
 873    }
 874
 875    fn render_thread_empty_state(
 876        &self,
 877        window: &mut Window,
 878        cx: &mut Context<Self>,
 879    ) -> impl IntoElement {
 880        let recent_history = self
 881            .history_store
 882            .update(cx, |this, cx| this.recent_entries(6, cx));
 883
 884        let configuration_error = self.configuration_error(cx);
 885        let no_error = configuration_error.is_none();
 886        let focus_handle = self.focus_handle(cx);
 887
 888        v_flex()
 889            .size_full()
 890            .when(recent_history.is_empty(), |this| {
 891                let configuration_error_ref = &configuration_error;
 892                this.child(
 893                    v_flex()
 894                        .size_full()
 895                        .max_w_80()
 896                        .mx_auto()
 897                        .justify_center()
 898                        .items_center()
 899                        .gap_1()
 900                        .child(
 901                            h_flex().child(
 902                                Headline::new("Welcome to the Agent Panel")
 903                            ),
 904                        )
 905                        .when(no_error, |parent| {
 906                            parent
 907                                .child(
 908                                    h_flex().child(
 909                                        Label::new("Ask and build anything.")
 910                                            .color(Color::Muted)
 911                                            .mb_2p5(),
 912                                    ),
 913                                )
 914                                .child(
 915                                    Button::new("new-thread", "Start New Thread")
 916                                        .icon(IconName::Plus)
 917                                        .icon_position(IconPosition::Start)
 918                                        .icon_size(IconSize::Small)
 919                                        .icon_color(Color::Muted)
 920                                        .full_width()
 921                                        .key_binding(KeyBinding::for_action_in(
 922                                            &NewThread::default(),
 923                                            &focus_handle,
 924                                            window,
 925                                            cx,
 926                                        ))
 927                                        .on_click(|_event, window, cx| {
 928                                            window.dispatch_action(NewThread::default().boxed_clone(), cx)
 929                                        }),
 930                                )
 931                                .child(
 932                                    Button::new("context", "Add Context")
 933                                        .icon(IconName::FileCode)
 934                                        .icon_position(IconPosition::Start)
 935                                        .icon_size(IconSize::Small)
 936                                        .icon_color(Color::Muted)
 937                                        .full_width()
 938                                        .key_binding(KeyBinding::for_action_in(
 939                                            &ToggleContextPicker,
 940                                            &focus_handle,
 941                                            window,
 942                                            cx,
 943                                        ))
 944                                        .on_click(|_event, window, cx| {
 945                                            window.dispatch_action(ToggleContextPicker.boxed_clone(), cx)
 946                                        }),
 947                                )
 948                                .child(
 949                                    Button::new("mode", "Switch Model")
 950                                        .icon(IconName::DatabaseZap)
 951                                        .icon_position(IconPosition::Start)
 952                                        .icon_size(IconSize::Small)
 953                                        .icon_color(Color::Muted)
 954                                        .full_width()
 955                                        .key_binding(KeyBinding::for_action_in(
 956                                            &ToggleModelSelector,
 957                                            &focus_handle,
 958                                            window,
 959                                            cx,
 960                                        ))
 961                                        .on_click(|_event, window, cx| {
 962                                            window.dispatch_action(ToggleModelSelector.boxed_clone(), cx)
 963                                        }),
 964                                )
 965                                .child(
 966                                    Button::new("settings", "View Settings")
 967                                        .icon(IconName::Settings)
 968                                        .icon_position(IconPosition::Start)
 969                                        .icon_size(IconSize::Small)
 970                                        .icon_color(Color::Muted)
 971                                        .full_width()
 972                                        .key_binding(KeyBinding::for_action_in(
 973                                            &OpenConfiguration,
 974                                            &focus_handle,
 975                                            window,
 976                                            cx,
 977                                        ))
 978                                        .on_click(|_event, window, cx| {
 979                                            window.dispatch_action(OpenConfiguration.boxed_clone(), cx)
 980                                        }),
 981                                )
 982                        })
 983                        .map(|parent| {
 984                            match configuration_error_ref {
 985                                Some(ConfigurationError::ProviderNotAuthenticated)
 986                                | Some(ConfigurationError::NoProvider) => {
 987                                    parent
 988                                        .child(
 989                                            h_flex().child(
 990                                                Label::new("To start using the agent, configure at least one LLM provider.")
 991                                                    .color(Color::Muted)
 992                                                    .mb_2p5()
 993                                            )
 994                                        )
 995                                        .child(
 996                                            Button::new("settings", "Configure a Provider")
 997                                                .icon(IconName::Settings)
 998                                                .icon_position(IconPosition::Start)
 999                                                .icon_size(IconSize::Small)
1000                                                .icon_color(Color::Muted)
1001                                                .full_width()
1002                                                .key_binding(KeyBinding::for_action_in(
1003                                                    &OpenConfiguration,
1004                                                    &focus_handle,
1005                                                    window,
1006                                                    cx,
1007                                                ))
1008                                                .on_click(|_event, window, cx| {
1009                                                    window.dispatch_action(OpenConfiguration.boxed_clone(), cx)
1010                                                }),
1011                                        )
1012                                }
1013                                Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
1014                                    parent.children(
1015                                        provider.render_accept_terms(
1016                                            LanguageModelProviderTosView::ThreadFreshStart,
1017                                            cx,
1018                                        ),
1019                                    )
1020                                }
1021                                None => parent,
1022                            }
1023                        })
1024                )
1025            })
1026            .when(!recent_history.is_empty(), |parent| {
1027                let focus_handle = focus_handle.clone();
1028                let configuration_error_ref = &configuration_error;
1029
1030                parent
1031                    .p_1p5()
1032                    .justify_end()
1033                    .gap_1()
1034                    .child(
1035                        h_flex()
1036                            .pl_1p5()
1037                            .pb_1()
1038                            .w_full()
1039                            .justify_between()
1040                            .border_b_1()
1041                            .border_color(cx.theme().colors().border_variant)
1042                            .child(
1043                                Label::new("Past Interactions")
1044                                    .size(LabelSize::Small)
1045                                    .color(Color::Muted),
1046                            )
1047                            .child(
1048                                Button::new("view-history", "View All")
1049                                    .style(ButtonStyle::Subtle)
1050                                    .label_size(LabelSize::Small)
1051                                    .key_binding(
1052                                        KeyBinding::for_action_in(
1053                                            &OpenHistory,
1054                                            &self.focus_handle(cx),
1055                                            window,
1056                                            cx,
1057                                        ).map(|kb| kb.size(rems_from_px(12.))),
1058                                    )
1059                                    .on_click(move |_event, window, cx| {
1060                                        window.dispatch_action(OpenHistory.boxed_clone(), cx);
1061                                    }),
1062                            ),
1063                    )
1064                    .child(
1065                        v_flex()
1066                            .gap_1()
1067                            .children(
1068                                recent_history.into_iter().map(|entry| {
1069                                    // TODO: Add keyboard navigation.
1070                                    match entry {
1071                                        HistoryEntry::Thread(thread) => {
1072                                            PastThread::new(thread, cx.entity().downgrade(), false)
1073                                                .into_any_element()
1074                                        }
1075                                        HistoryEntry::Context(context) => {
1076                                            PastContext::new(context, cx.entity().downgrade(), false)
1077                                                .into_any_element()
1078                                        }
1079                                    }
1080                                }),
1081                            )
1082                    )
1083                    .map(|parent| {
1084                        match configuration_error_ref {
1085                            Some(ConfigurationError::ProviderNotAuthenticated)
1086                            | Some(ConfigurationError::NoProvider) => {
1087                                parent
1088                                    .child(
1089                                        Banner::new()
1090                                            .severity(ui::Severity::Warning)
1091                                            .children(
1092                                                Label::new(
1093                                                    "Configure at least one LLM provider to start using the panel.",
1094                                                )
1095                                                .size(LabelSize::Small),
1096                                            )
1097                                            .action_slot(
1098                                                Button::new("settings", "Configure Provider")
1099                                                    .style(ButtonStyle::Tinted(ui::TintColor::Warning))
1100                                                    .label_size(LabelSize::Small)
1101                                                    .key_binding(
1102                                                        KeyBinding::for_action_in(
1103                                                            &OpenConfiguration,
1104                                                            &focus_handle,
1105                                                            window,
1106                                                            cx,
1107                                                        )
1108                                                        .map(|kb| kb.size(rems_from_px(12.))),
1109                                                    )
1110                                                    .on_click(|_event, window, cx| {
1111                                                        window.dispatch_action(
1112                                                            OpenConfiguration.boxed_clone(),
1113                                                            cx,
1114                                                        )
1115                                                    }),
1116                                            ),
1117                                    )
1118                            }
1119                            Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
1120                                parent
1121                                    .child(
1122                                        Banner::new()
1123                                            .severity(ui::Severity::Warning)
1124                                            .children(
1125                                                h_flex()
1126                                                    .w_full()
1127                                                    .children(
1128                                                        provider.render_accept_terms(
1129                                                            LanguageModelProviderTosView::ThreadtEmptyState,
1130                                                            cx,
1131                                                        ),
1132                                                    ),
1133                                            ),
1134                                    )
1135                            }
1136                            None => parent,
1137                        }
1138                    })
1139            })
1140    }
1141
1142    fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
1143        let last_error = self.thread.read(cx).last_error()?;
1144
1145        Some(
1146            div()
1147                .absolute()
1148                .right_3()
1149                .bottom_12()
1150                .max_w_96()
1151                .py_2()
1152                .px_3()
1153                .elevation_2(cx)
1154                .occlude()
1155                .child(match last_error {
1156                    ThreadError::PaymentRequired => self.render_payment_required_error(cx),
1157                    ThreadError::MaxMonthlySpendReached => {
1158                        self.render_max_monthly_spend_reached_error(cx)
1159                    }
1160                    ThreadError::Message { header, message } => {
1161                        self.render_error_message(header, message, cx)
1162                    }
1163                })
1164                .into_any(),
1165        )
1166    }
1167
1168    fn render_payment_required_error(&self, cx: &mut Context<Self>) -> AnyElement {
1169        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.";
1170
1171        v_flex()
1172            .gap_0p5()
1173            .child(
1174                h_flex()
1175                    .gap_1p5()
1176                    .items_center()
1177                    .child(Icon::new(IconName::XCircle).color(Color::Error))
1178                    .child(Label::new("Free Usage Exceeded").weight(FontWeight::MEDIUM)),
1179            )
1180            .child(
1181                div()
1182                    .id("error-message")
1183                    .max_h_24()
1184                    .overflow_y_scroll()
1185                    .child(Label::new(ERROR_MESSAGE)),
1186            )
1187            .child(
1188                h_flex()
1189                    .justify_end()
1190                    .mt_1()
1191                    .child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
1192                        |this, _, _, cx| {
1193                            this.thread.update(cx, |this, _cx| {
1194                                this.clear_last_error();
1195                            });
1196
1197                            cx.open_url(&zed_urls::account_url(cx));
1198                            cx.notify();
1199                        },
1200                    )))
1201                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
1202                        |this, _, _, cx| {
1203                            this.thread.update(cx, |this, _cx| {
1204                                this.clear_last_error();
1205                            });
1206
1207                            cx.notify();
1208                        },
1209                    ))),
1210            )
1211            .into_any()
1212    }
1213
1214    fn render_max_monthly_spend_reached_error(&self, cx: &mut Context<Self>) -> AnyElement {
1215        const ERROR_MESSAGE: &str = "You have reached your maximum monthly spend. Increase your spend limit to continue using Zed LLMs.";
1216
1217        v_flex()
1218            .gap_0p5()
1219            .child(
1220                h_flex()
1221                    .gap_1p5()
1222                    .items_center()
1223                    .child(Icon::new(IconName::XCircle).color(Color::Error))
1224                    .child(Label::new("Max Monthly Spend Reached").weight(FontWeight::MEDIUM)),
1225            )
1226            .child(
1227                div()
1228                    .id("error-message")
1229                    .max_h_24()
1230                    .overflow_y_scroll()
1231                    .child(Label::new(ERROR_MESSAGE)),
1232            )
1233            .child(
1234                h_flex()
1235                    .justify_end()
1236                    .mt_1()
1237                    .child(
1238                        Button::new("subscribe", "Update Monthly Spend Limit").on_click(
1239                            cx.listener(|this, _, _, cx| {
1240                                this.thread.update(cx, |this, _cx| {
1241                                    this.clear_last_error();
1242                                });
1243
1244                                cx.open_url(&zed_urls::account_url(cx));
1245                                cx.notify();
1246                            }),
1247                        ),
1248                    )
1249                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
1250                        |this, _, _, cx| {
1251                            this.thread.update(cx, |this, _cx| {
1252                                this.clear_last_error();
1253                            });
1254
1255                            cx.notify();
1256                        },
1257                    ))),
1258            )
1259            .into_any()
1260    }
1261
1262    fn render_error_message(
1263        &self,
1264        header: SharedString,
1265        message: SharedString,
1266        cx: &mut Context<Self>,
1267    ) -> AnyElement {
1268        v_flex()
1269            .gap_0p5()
1270            .child(
1271                h_flex()
1272                    .gap_1p5()
1273                    .items_center()
1274                    .child(Icon::new(IconName::XCircle).color(Color::Error))
1275                    .child(Label::new(header).weight(FontWeight::MEDIUM)),
1276            )
1277            .child(
1278                div()
1279                    .id("error-message")
1280                    .max_h_32()
1281                    .overflow_y_scroll()
1282                    .child(Label::new(message)),
1283            )
1284            .child(
1285                h_flex()
1286                    .justify_end()
1287                    .mt_1()
1288                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
1289                        |this, _, _, cx| {
1290                            this.thread.update(cx, |this, _cx| {
1291                                this.clear_last_error();
1292                            });
1293
1294                            cx.notify();
1295                        },
1296                    ))),
1297            )
1298            .into_any()
1299    }
1300
1301    fn key_context(&self) -> KeyContext {
1302        let mut key_context = KeyContext::new_with_defaults();
1303        key_context.add("AgentPanel");
1304        if matches!(self.active_view, ActiveView::PromptEditor) {
1305            key_context.add("prompt_editor");
1306        }
1307        key_context
1308    }
1309}
1310
1311impl Render for AssistantPanel {
1312    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1313        v_flex()
1314            .key_context(self.key_context())
1315            .justify_between()
1316            .size_full()
1317            .on_action(cx.listener(Self::cancel))
1318            .on_action(cx.listener(|this, action: &NewThread, window, cx| {
1319                this.new_thread(action, window, cx);
1320            }))
1321            .on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
1322                this.open_history(window, cx);
1323            }))
1324            .on_action(cx.listener(|this, _: &OpenConfiguration, window, cx| {
1325                this.open_configuration(window, cx);
1326            }))
1327            .on_action(cx.listener(Self::open_active_thread_as_markdown))
1328            .on_action(cx.listener(Self::deploy_prompt_library))
1329            .on_action(cx.listener(Self::open_agent_diff))
1330            .child(self.render_toolbar(window, cx))
1331            .map(|parent| match self.active_view {
1332                ActiveView::Thread => parent
1333                    .child(self.render_active_thread_or_empty_state(window, cx))
1334                    .child(h_flex().child(self.message_editor.clone()))
1335                    .children(self.render_last_error(cx)),
1336                ActiveView::History => parent.child(self.history.clone()),
1337                ActiveView::PromptEditor => parent.children(self.context_editor.clone()),
1338                ActiveView::Configuration => parent.children(self.configuration.clone()),
1339            })
1340    }
1341}
1342
1343struct PromptLibraryInlineAssist {
1344    workspace: WeakEntity<Workspace>,
1345}
1346
1347impl PromptLibraryInlineAssist {
1348    pub fn new(workspace: WeakEntity<Workspace>) -> Self {
1349        Self { workspace }
1350    }
1351}
1352
1353impl prompt_library::InlineAssistDelegate for PromptLibraryInlineAssist {
1354    fn assist(
1355        &self,
1356        prompt_editor: &Entity<Editor>,
1357        _initial_prompt: Option<String>,
1358        window: &mut Window,
1359        cx: &mut Context<PromptLibrary>,
1360    ) {
1361        InlineAssistant::update_global(cx, |assistant, cx| {
1362            assistant.assist(&prompt_editor, self.workspace.clone(), None, window, cx)
1363        })
1364    }
1365
1366    fn focus_assistant_panel(
1367        &self,
1368        workspace: &mut Workspace,
1369        window: &mut Window,
1370        cx: &mut Context<Workspace>,
1371    ) -> bool {
1372        workspace
1373            .focus_panel::<AssistantPanel>(window, cx)
1374            .is_some()
1375    }
1376}
1377
1378pub struct ConcreteAssistantPanelDelegate;
1379
1380impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
1381    fn active_context_editor(
1382        &self,
1383        workspace: &mut Workspace,
1384        _window: &mut Window,
1385        cx: &mut Context<Workspace>,
1386    ) -> Option<Entity<ContextEditor>> {
1387        let panel = workspace.panel::<AssistantPanel>(cx)?;
1388        panel.update(cx, |panel, _cx| panel.context_editor.clone())
1389    }
1390
1391    fn open_saved_context(
1392        &self,
1393        workspace: &mut Workspace,
1394        path: std::path::PathBuf,
1395        window: &mut Window,
1396        cx: &mut Context<Workspace>,
1397    ) -> Task<Result<()>> {
1398        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
1399            return Task::ready(Err(anyhow!("Agent panel not found")));
1400        };
1401
1402        panel.update(cx, |panel, cx| {
1403            panel.open_saved_prompt_editor(path, window, cx)
1404        })
1405    }
1406
1407    fn open_remote_context(
1408        &self,
1409        _workspace: &mut Workspace,
1410        _context_id: assistant_context_editor::ContextId,
1411        _window: &mut Window,
1412        _cx: &mut Context<Workspace>,
1413    ) -> Task<Result<Entity<ContextEditor>>> {
1414        Task::ready(Err(anyhow!("opening remote context not implemented")))
1415    }
1416
1417    fn quote_selection(
1418        &self,
1419        _workspace: &mut Workspace,
1420        _creases: Vec<(String, String)>,
1421        _window: &mut Window,
1422        _cx: &mut Context<Workspace>,
1423    ) {
1424    }
1425}