agent_panel.rs

   1use std::cell::RefCell;
   2use std::ops::{Not, Range};
   3use std::path::Path;
   4use std::rc::Rc;
   5use std::sync::Arc;
   6use std::time::Duration;
   7
   8use agent_servers::AgentServer;
   9use db::kvp::{Dismissable, KEY_VALUE_STORE};
  10use serde::{Deserialize, Serialize};
  11
  12use crate::NewExternalAgentThread;
  13use crate::agent_diff::AgentDiffThread;
  14use crate::ui::NewThreadButton;
  15use crate::{
  16    AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode,
  17    DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
  18    NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell,
  19    ResetTrialUpsell, ToggleBurnMode, ToggleContextPicker, ToggleNavigationMenu, ToggleOptionsMenu,
  20    acp::AcpThreadView,
  21    active_thread::{self, ActiveThread, ActiveThreadEvent},
  22    agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
  23    agent_diff::AgentDiff,
  24    message_editor::{MessageEditor, MessageEditorEvent},
  25    slash_command::SlashCommandCompletionProvider,
  26    text_thread_editor::{
  27        AgentPanelDelegate, TextThreadEditor, humanize_token_count, make_lsp_adapter_delegate,
  28        render_remaining_tokens,
  29    },
  30    thread_history::{HistoryEntryElement, ThreadHistory},
  31    ui::{AgentOnboardingModal, EndTrialUpsell},
  32};
  33use agent::{
  34    Thread, ThreadError, ThreadEvent, ThreadId, ThreadSummary, TokenUsageRatio,
  35    context_store::ContextStore,
  36    history_store::{HistoryEntryId, HistoryStore},
  37    thread_store::{TextThreadStore, ThreadStore},
  38};
  39use agent_settings::{AgentDockPosition, AgentSettings, CompletionMode, DefaultView};
  40use ai_onboarding::AgentPanelOnboarding;
  41use anyhow::{Result, anyhow};
  42use assistant_context::{AssistantContext, ContextEvent, ContextSummary};
  43use assistant_slash_command::SlashCommandWorkingSet;
  44use assistant_tool::ToolWorkingSet;
  45use client::{UserStore, zed_urls};
  46use cloud_llm_client::{CompletionIntent, Plan, UsageLimit};
  47use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
  48use feature_flags::{self, FeatureFlagAppExt};
  49use fs::Fs;
  50use gpui::{
  51    Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, ClipboardItem,
  52    Corner, DismissEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, Hsla,
  53    KeyContext, Pixels, Subscription, Task, UpdateGlobal, WeakEntity, prelude::*,
  54    pulsating_between,
  55};
  56use language::LanguageRegistry;
  57use language_model::{
  58    ConfigurationError, ConfiguredModel, LanguageModelProviderTosView, LanguageModelRegistry,
  59};
  60use project::{DisableAiSettings, Project, ProjectPath, Worktree};
  61use prompt_store::{PromptBuilder, PromptStore, UserPromptId};
  62use rules_library::{RulesLibrary, open_rules_library};
  63use search::{BufferSearchBar, buffer_search};
  64use settings::{Settings, update_settings_file};
  65use theme::ThemeSettings;
  66use time::UtcOffset;
  67use ui::utils::WithRemSize;
  68use ui::{
  69    Banner, Callout, ContextMenu, ContextMenuEntry, ElevationIndex, KeyBinding, PopoverMenu,
  70    PopoverMenuHandle, ProgressBar, Tab, Tooltip, prelude::*,
  71};
  72use util::ResultExt as _;
  73use workspace::{
  74    CollaboratorId, DraggedSelection, DraggedTab, ToggleZoom, ToolbarItemView, Workspace,
  75    dock::{DockPosition, Panel, PanelEvent},
  76};
  77use zed_actions::{
  78    DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize,
  79    agent::{OpenOnboardingModal, OpenSettings, ResetOnboarding, ToggleModelSelector},
  80    assistant::{OpenRulesLibrary, ToggleFocus},
  81};
  82
  83const AGENT_PANEL_KEY: &str = "agent_panel";
  84
  85#[derive(Serialize, Deserialize)]
  86struct SerializedAgentPanel {
  87    width: Option<Pixels>,
  88}
  89
  90pub fn init(cx: &mut App) {
  91    cx.observe_new(
  92        |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
  93            workspace
  94                .register_action(|workspace, action: &NewThread, window, cx| {
  95                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
  96                        panel.update(cx, |panel, cx| panel.new_thread(action, window, cx));
  97                        workspace.focus_panel::<AgentPanel>(window, cx);
  98                    }
  99                })
 100                .register_action(|workspace, _: &OpenHistory, window, cx| {
 101                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 102                        workspace.focus_panel::<AgentPanel>(window, cx);
 103                        panel.update(cx, |panel, cx| panel.open_history(window, cx));
 104                    }
 105                })
 106                .register_action(|workspace, _: &OpenSettings, window, cx| {
 107                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 108                        workspace.focus_panel::<AgentPanel>(window, cx);
 109                        panel.update(cx, |panel, cx| panel.open_configuration(window, cx));
 110                    }
 111                })
 112                .register_action(|workspace, _: &NewTextThread, window, cx| {
 113                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 114                        workspace.focus_panel::<AgentPanel>(window, cx);
 115                        panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));
 116                    }
 117                })
 118                .register_action(|workspace, action: &NewExternalAgentThread, window, cx| {
 119                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 120                        workspace.focus_panel::<AgentPanel>(window, cx);
 121                        panel.update(cx, |panel, cx| {
 122                            panel.new_external_thread(action.agent, window, cx)
 123                        });
 124                    }
 125                })
 126                .register_action(|workspace, action: &OpenRulesLibrary, window, cx| {
 127                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 128                        workspace.focus_panel::<AgentPanel>(window, cx);
 129                        panel.update(cx, |panel, cx| {
 130                            panel.deploy_rules_library(action, window, cx)
 131                        });
 132                    }
 133                })
 134                .register_action(|workspace, _: &OpenAgentDiff, window, cx| {
 135                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 136                        workspace.focus_panel::<AgentPanel>(window, cx);
 137                        match &panel.read(cx).active_view {
 138                            ActiveView::Thread { thread, .. } => {
 139                                let thread = thread.read(cx).thread().clone();
 140                                AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx);
 141                            }
 142                            ActiveView::ExternalAgentThread { .. }
 143                            | ActiveView::TextThread { .. }
 144                            | ActiveView::History
 145                            | ActiveView::Configuration => {}
 146                        }
 147                    }
 148                })
 149                .register_action(|workspace, _: &Follow, window, cx| {
 150                    workspace.follow(CollaboratorId::Agent, window, cx);
 151                })
 152                .register_action(|workspace, _: &ExpandMessageEditor, window, cx| {
 153                    let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
 154                        return;
 155                    };
 156                    workspace.focus_panel::<AgentPanel>(window, cx);
 157                    panel.update(cx, |panel, cx| {
 158                        if let Some(message_editor) = panel.active_message_editor() {
 159                            message_editor.update(cx, |editor, cx| {
 160                                editor.expand_message_editor(&ExpandMessageEditor, window, cx);
 161                            });
 162                        }
 163                    });
 164                })
 165                .register_action(|workspace, _: &ToggleNavigationMenu, window, cx| {
 166                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 167                        workspace.focus_panel::<AgentPanel>(window, cx);
 168                        panel.update(cx, |panel, cx| {
 169                            panel.toggle_navigation_menu(&ToggleNavigationMenu, window, cx);
 170                        });
 171                    }
 172                })
 173                .register_action(|workspace, _: &ToggleOptionsMenu, window, cx| {
 174                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 175                        workspace.focus_panel::<AgentPanel>(window, cx);
 176                        panel.update(cx, |panel, cx| {
 177                            panel.toggle_options_menu(&ToggleOptionsMenu, window, cx);
 178                        });
 179                    }
 180                })
 181                .register_action(|workspace, _: &OpenOnboardingModal, window, cx| {
 182                    AgentOnboardingModal::toggle(workspace, window, cx)
 183                })
 184                .register_action(|_workspace, _: &ResetOnboarding, window, cx| {
 185                    window.dispatch_action(workspace::RestoreBanner.boxed_clone(), cx);
 186                    window.refresh();
 187                })
 188                .register_action(|_workspace, _: &ResetTrialUpsell, _window, cx| {
 189                    OnboardingUpsell::set_dismissed(false, cx);
 190                })
 191                .register_action(|_workspace, _: &ResetTrialEndUpsell, _window, cx| {
 192                    TrialEndUpsell::set_dismissed(false, cx);
 193                });
 194        },
 195    )
 196    .detach();
 197}
 198
 199enum ActiveView {
 200    Thread {
 201        thread: Entity<ActiveThread>,
 202        change_title_editor: Entity<Editor>,
 203        message_editor: Entity<MessageEditor>,
 204        _subscriptions: Vec<gpui::Subscription>,
 205    },
 206    ExternalAgentThread {
 207        thread_view: Entity<AcpThreadView>,
 208    },
 209    TextThread {
 210        context_editor: Entity<TextThreadEditor>,
 211        title_editor: Entity<Editor>,
 212        buffer_search_bar: Entity<BufferSearchBar>,
 213        _subscriptions: Vec<gpui::Subscription>,
 214    },
 215    History,
 216    Configuration,
 217}
 218
 219enum WhichFontSize {
 220    AgentFont,
 221    BufferFont,
 222    None,
 223}
 224
 225impl ActiveView {
 226    pub fn which_font_size_used(&self) -> WhichFontSize {
 227        match self {
 228            ActiveView::Thread { .. }
 229            | ActiveView::ExternalAgentThread { .. }
 230            | ActiveView::History => WhichFontSize::AgentFont,
 231            ActiveView::TextThread { .. } => WhichFontSize::BufferFont,
 232            ActiveView::Configuration => WhichFontSize::None,
 233        }
 234    }
 235
 236    pub fn thread(
 237        active_thread: Entity<ActiveThread>,
 238        message_editor: Entity<MessageEditor>,
 239        window: &mut Window,
 240        cx: &mut Context<AgentPanel>,
 241    ) -> Self {
 242        let summary = active_thread.read(cx).summary(cx).or_default();
 243
 244        let editor = cx.new(|cx| {
 245            let mut editor = Editor::single_line(window, cx);
 246            editor.set_text(summary.clone(), window, cx);
 247            editor
 248        });
 249
 250        let subscriptions = vec![
 251            cx.subscribe(&message_editor, |this, _, event, cx| match event {
 252                MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => {
 253                    cx.notify();
 254                }
 255                MessageEditorEvent::ScrollThreadToBottom => match &this.active_view {
 256                    ActiveView::Thread { thread, .. } => {
 257                        thread.update(cx, |thread, cx| {
 258                            thread.scroll_to_bottom(cx);
 259                        });
 260                    }
 261                    ActiveView::ExternalAgentThread { .. } => {}
 262                    ActiveView::TextThread { .. }
 263                    | ActiveView::History
 264                    | ActiveView::Configuration => {}
 265                },
 266            }),
 267            window.subscribe(&editor, cx, {
 268                {
 269                    let thread = active_thread.clone();
 270                    move |editor, event, window, cx| match event {
 271                        EditorEvent::BufferEdited => {
 272                            let new_summary = editor.read(cx).text(cx);
 273
 274                            thread.update(cx, |thread, cx| {
 275                                thread.thread().update(cx, |thread, cx| {
 276                                    thread.set_summary(new_summary, cx);
 277                                });
 278                            })
 279                        }
 280                        EditorEvent::Blurred => {
 281                            if editor.read(cx).text(cx).is_empty() {
 282                                let summary = thread.read(cx).summary(cx).or_default();
 283
 284                                editor.update(cx, |editor, cx| {
 285                                    editor.set_text(summary, window, cx);
 286                                });
 287                            }
 288                        }
 289                        _ => {}
 290                    }
 291                }
 292            }),
 293            cx.subscribe(&active_thread, |_, _, event, cx| match &event {
 294                ActiveThreadEvent::EditingMessageTokenCountChanged => {
 295                    cx.notify();
 296                }
 297            }),
 298            cx.subscribe_in(&active_thread.read(cx).thread().clone(), window, {
 299                let editor = editor.clone();
 300                move |_, thread, event, window, cx| match event {
 301                    ThreadEvent::SummaryGenerated => {
 302                        let summary = thread.read(cx).summary().or_default();
 303
 304                        editor.update(cx, |editor, cx| {
 305                            editor.set_text(summary, window, cx);
 306                        })
 307                    }
 308                    ThreadEvent::MessageAdded(_) => {
 309                        cx.notify();
 310                    }
 311                    _ => {}
 312                }
 313            }),
 314        ];
 315
 316        Self::Thread {
 317            change_title_editor: editor,
 318            thread: active_thread,
 319            message_editor: message_editor,
 320            _subscriptions: subscriptions,
 321        }
 322    }
 323
 324    pub fn prompt_editor(
 325        context_editor: Entity<TextThreadEditor>,
 326        history_store: Entity<HistoryStore>,
 327        language_registry: Arc<LanguageRegistry>,
 328        window: &mut Window,
 329        cx: &mut App,
 330    ) -> Self {
 331        let title = context_editor.read(cx).title(cx).to_string();
 332
 333        let editor = cx.new(|cx| {
 334            let mut editor = Editor::single_line(window, cx);
 335            editor.set_text(title, window, cx);
 336            editor
 337        });
 338
 339        // This is a workaround for `editor.set_text` emitting a `BufferEdited` event, which would
 340        // cause a custom summary to be set. The presence of this custom summary would cause
 341        // summarization to not happen.
 342        let mut suppress_first_edit = true;
 343
 344        let subscriptions = vec![
 345            window.subscribe(&editor, cx, {
 346                {
 347                    let context_editor = context_editor.clone();
 348                    move |editor, event, window, cx| match event {
 349                        EditorEvent::BufferEdited => {
 350                            if suppress_first_edit {
 351                                suppress_first_edit = false;
 352                                return;
 353                            }
 354                            let new_summary = editor.read(cx).text(cx);
 355
 356                            context_editor.update(cx, |context_editor, cx| {
 357                                context_editor
 358                                    .context()
 359                                    .update(cx, |assistant_context, cx| {
 360                                        assistant_context.set_custom_summary(new_summary, cx);
 361                                    })
 362                            })
 363                        }
 364                        EditorEvent::Blurred => {
 365                            if editor.read(cx).text(cx).is_empty() {
 366                                let summary = context_editor
 367                                    .read(cx)
 368                                    .context()
 369                                    .read(cx)
 370                                    .summary()
 371                                    .or_default();
 372
 373                                editor.update(cx, |editor, cx| {
 374                                    editor.set_text(summary, window, cx);
 375                                });
 376                            }
 377                        }
 378                        _ => {}
 379                    }
 380                }
 381            }),
 382            window.subscribe(&context_editor.read(cx).context().clone(), cx, {
 383                let editor = editor.clone();
 384                move |assistant_context, event, window, cx| match event {
 385                    ContextEvent::SummaryGenerated => {
 386                        let summary = assistant_context.read(cx).summary().or_default();
 387
 388                        editor.update(cx, |editor, cx| {
 389                            editor.set_text(summary, window, cx);
 390                        })
 391                    }
 392                    ContextEvent::PathChanged { old_path, new_path } => {
 393                        history_store.update(cx, |history_store, cx| {
 394                            if let Some(old_path) = old_path {
 395                                history_store
 396                                    .replace_recently_opened_text_thread(old_path, new_path, cx);
 397                            } else {
 398                                history_store.push_recently_opened_entry(
 399                                    HistoryEntryId::Context(new_path.clone()),
 400                                    cx,
 401                                );
 402                            }
 403                        });
 404                    }
 405                    _ => {}
 406                }
 407            }),
 408        ];
 409
 410        let buffer_search_bar =
 411            cx.new(|cx| BufferSearchBar::new(Some(language_registry), window, cx));
 412        buffer_search_bar.update(cx, |buffer_search_bar, cx| {
 413            buffer_search_bar.set_active_pane_item(Some(&context_editor), window, cx)
 414        });
 415
 416        Self::TextThread {
 417            context_editor,
 418            title_editor: editor,
 419            buffer_search_bar,
 420            _subscriptions: subscriptions,
 421        }
 422    }
 423}
 424
 425pub struct AgentPanel {
 426    workspace: WeakEntity<Workspace>,
 427    user_store: Entity<UserStore>,
 428    project: Entity<Project>,
 429    fs: Arc<dyn Fs>,
 430    language_registry: Arc<LanguageRegistry>,
 431    thread_store: Entity<ThreadStore>,
 432    _default_model_subscription: Subscription,
 433    context_store: Entity<TextThreadStore>,
 434    prompt_store: Option<Entity<PromptStore>>,
 435    inline_assist_context_store: Entity<ContextStore>,
 436    configuration: Option<Entity<AgentConfiguration>>,
 437    configuration_subscription: Option<Subscription>,
 438    local_timezone: UtcOffset,
 439    active_view: ActiveView,
 440    acp_message_history:
 441        Rc<RefCell<crate::acp::MessageHistory<Vec<agent_client_protocol::ContentBlock>>>>,
 442    previous_view: Option<ActiveView>,
 443    history_store: Entity<HistoryStore>,
 444    history: Entity<ThreadHistory>,
 445    hovered_recent_history_item: Option<usize>,
 446    new_thread_menu_handle: PopoverMenuHandle<ContextMenu>,
 447    agent_panel_menu_handle: PopoverMenuHandle<ContextMenu>,
 448    assistant_navigation_menu_handle: PopoverMenuHandle<ContextMenu>,
 449    assistant_navigation_menu: Option<Entity<ContextMenu>>,
 450    width: Option<Pixels>,
 451    height: Option<Pixels>,
 452    zoomed: bool,
 453    pending_serialization: Option<Task<Result<()>>>,
 454    onboarding: Entity<AgentPanelOnboarding>,
 455}
 456
 457impl AgentPanel {
 458    fn serialize(&mut self, cx: &mut Context<Self>) {
 459        let width = self.width;
 460        self.pending_serialization = Some(cx.background_spawn(async move {
 461            KEY_VALUE_STORE
 462                .write_kvp(
 463                    AGENT_PANEL_KEY.into(),
 464                    serde_json::to_string(&SerializedAgentPanel { width })?,
 465                )
 466                .await?;
 467            anyhow::Ok(())
 468        }));
 469    }
 470    pub fn load(
 471        workspace: WeakEntity<Workspace>,
 472        prompt_builder: Arc<PromptBuilder>,
 473        mut cx: AsyncWindowContext,
 474    ) -> Task<Result<Entity<Self>>> {
 475        let prompt_store = cx.update(|_window, cx| PromptStore::global(cx));
 476        cx.spawn(async move |cx| {
 477            let prompt_store = match prompt_store {
 478                Ok(prompt_store) => prompt_store.await.ok(),
 479                Err(_) => None,
 480            };
 481            let tools = cx.new(|_| ToolWorkingSet::default())?;
 482            let thread_store = workspace
 483                .update(cx, |workspace, cx| {
 484                    let project = workspace.project().clone();
 485                    ThreadStore::load(
 486                        project,
 487                        tools.clone(),
 488                        prompt_store.clone(),
 489                        prompt_builder.clone(),
 490                        cx,
 491                    )
 492                })?
 493                .await?;
 494
 495            let slash_commands = Arc::new(SlashCommandWorkingSet::default());
 496            let context_store = workspace
 497                .update(cx, |workspace, cx| {
 498                    let project = workspace.project().clone();
 499                    assistant_context::ContextStore::new(
 500                        project,
 501                        prompt_builder.clone(),
 502                        slash_commands,
 503                        cx,
 504                    )
 505                })?
 506                .await?;
 507
 508            let serialized_panel = if let Some(panel) = cx
 509                .background_spawn(async move { KEY_VALUE_STORE.read_kvp(AGENT_PANEL_KEY) })
 510                .await
 511                .log_err()
 512                .flatten()
 513            {
 514                Some(serde_json::from_str::<SerializedAgentPanel>(&panel)?)
 515            } else {
 516                None
 517            };
 518
 519            let panel = workspace.update_in(cx, |workspace, window, cx| {
 520                let panel = cx.new(|cx| {
 521                    Self::new(
 522                        workspace,
 523                        thread_store,
 524                        context_store,
 525                        prompt_store,
 526                        window,
 527                        cx,
 528                    )
 529                });
 530                if let Some(serialized_panel) = serialized_panel {
 531                    panel.update(cx, |panel, cx| {
 532                        panel.width = serialized_panel.width.map(|w| w.round());
 533                        cx.notify();
 534                    });
 535                }
 536                panel
 537            })?;
 538
 539            Ok(panel)
 540        })
 541    }
 542
 543    fn new(
 544        workspace: &Workspace,
 545        thread_store: Entity<ThreadStore>,
 546        context_store: Entity<TextThreadStore>,
 547        prompt_store: Option<Entity<PromptStore>>,
 548        window: &mut Window,
 549        cx: &mut Context<Self>,
 550    ) -> Self {
 551        let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
 552        let fs = workspace.app_state().fs.clone();
 553        let user_store = workspace.app_state().user_store.clone();
 554        let project = workspace.project();
 555        let language_registry = project.read(cx).languages().clone();
 556        let client = workspace.client().clone();
 557        let workspace = workspace.weak_handle();
 558        let weak_self = cx.entity().downgrade();
 559
 560        let message_editor_context_store =
 561            cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
 562        let inline_assist_context_store =
 563            cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
 564
 565        let thread_id = thread.read(cx).id().clone();
 566
 567        let history_store = cx.new(|cx| {
 568            HistoryStore::new(
 569                thread_store.clone(),
 570                context_store.clone(),
 571                [HistoryEntryId::Thread(thread_id)],
 572                cx,
 573            )
 574        });
 575
 576        let message_editor = cx.new(|cx| {
 577            MessageEditor::new(
 578                fs.clone(),
 579                workspace.clone(),
 580                message_editor_context_store.clone(),
 581                prompt_store.clone(),
 582                thread_store.downgrade(),
 583                context_store.downgrade(),
 584                Some(history_store.downgrade()),
 585                thread.clone(),
 586                window,
 587                cx,
 588            )
 589        });
 590
 591        cx.observe(&history_store, |_, _, cx| cx.notify()).detach();
 592
 593        let active_thread = cx.new(|cx| {
 594            ActiveThread::new(
 595                thread.clone(),
 596                thread_store.clone(),
 597                context_store.clone(),
 598                message_editor_context_store.clone(),
 599                language_registry.clone(),
 600                workspace.clone(),
 601                window,
 602                cx,
 603            )
 604        });
 605
 606        let panel_type = AgentSettings::get_global(cx).default_view;
 607        let active_view = match panel_type {
 608            DefaultView::Thread => ActiveView::thread(active_thread, message_editor, window, cx),
 609            DefaultView::TextThread => {
 610                let context =
 611                    context_store.update(cx, |context_store, cx| context_store.create(cx));
 612                let lsp_adapter_delegate = make_lsp_adapter_delegate(&project.clone(), cx).unwrap();
 613                let context_editor = cx.new(|cx| {
 614                    let mut editor = TextThreadEditor::for_context(
 615                        context,
 616                        fs.clone(),
 617                        workspace.clone(),
 618                        project.clone(),
 619                        lsp_adapter_delegate,
 620                        window,
 621                        cx,
 622                    );
 623                    editor.insert_default_prompt(window, cx);
 624                    editor
 625                });
 626                ActiveView::prompt_editor(
 627                    context_editor,
 628                    history_store.clone(),
 629                    language_registry.clone(),
 630                    window,
 631                    cx,
 632                )
 633            }
 634        };
 635
 636        AgentDiff::set_active_thread(&workspace, thread.clone(), window, cx);
 637
 638        let weak_panel = weak_self.clone();
 639
 640        window.defer(cx, move |window, cx| {
 641            let panel = weak_panel.clone();
 642            let assistant_navigation_menu =
 643                ContextMenu::build_persistent(window, cx, move |mut menu, _window, cx| {
 644                    if let Some(panel) = panel.upgrade() {
 645                        menu = Self::populate_recently_opened_menu_section(menu, panel, cx);
 646                    }
 647                    menu.action("View All", Box::new(OpenHistory))
 648                        .end_slot_action(DeleteRecentlyOpenThread.boxed_clone())
 649                        .fixed_width(px(320.).into())
 650                        .keep_open_on_confirm(false)
 651                        .key_context("NavigationMenu")
 652                });
 653            weak_panel
 654                .update(cx, |panel, cx| {
 655                    cx.subscribe_in(
 656                        &assistant_navigation_menu,
 657                        window,
 658                        |_, menu, _: &DismissEvent, window, cx| {
 659                            menu.update(cx, |menu, _| {
 660                                menu.clear_selected();
 661                            });
 662                            cx.focus_self(window);
 663                        },
 664                    )
 665                    .detach();
 666                    panel.assistant_navigation_menu = Some(assistant_navigation_menu);
 667                })
 668                .ok();
 669        });
 670
 671        let _default_model_subscription = cx.subscribe(
 672            &LanguageModelRegistry::global(cx),
 673            |this, _, event: &language_model::Event, cx| match event {
 674                language_model::Event::DefaultModelChanged => match &this.active_view {
 675                    ActiveView::Thread { thread, .. } => {
 676                        thread
 677                            .read(cx)
 678                            .thread()
 679                            .clone()
 680                            .update(cx, |thread, cx| thread.get_or_init_configured_model(cx));
 681                    }
 682                    ActiveView::ExternalAgentThread { .. }
 683                    | ActiveView::TextThread { .. }
 684                    | ActiveView::History
 685                    | ActiveView::Configuration => {}
 686                },
 687                _ => {}
 688            },
 689        );
 690
 691        let onboarding = cx.new(|cx| {
 692            AgentPanelOnboarding::new(
 693                user_store.clone(),
 694                client,
 695                |_window, cx| {
 696                    OnboardingUpsell::set_dismissed(true, cx);
 697                },
 698                cx,
 699            )
 700        });
 701
 702        Self {
 703            active_view,
 704            workspace,
 705            user_store,
 706            project: project.clone(),
 707            fs: fs.clone(),
 708            language_registry,
 709            thread_store: thread_store.clone(),
 710            _default_model_subscription,
 711            context_store,
 712            prompt_store,
 713            configuration: None,
 714            configuration_subscription: None,
 715            local_timezone: UtcOffset::from_whole_seconds(
 716                chrono::Local::now().offset().local_minus_utc(),
 717            )
 718            .unwrap(),
 719            inline_assist_context_store,
 720            previous_view: None,
 721            acp_message_history: Default::default(),
 722            history_store: history_store.clone(),
 723            history: cx.new(|cx| ThreadHistory::new(weak_self, history_store, window, cx)),
 724            hovered_recent_history_item: None,
 725            new_thread_menu_handle: PopoverMenuHandle::default(),
 726            agent_panel_menu_handle: PopoverMenuHandle::default(),
 727            assistant_navigation_menu_handle: PopoverMenuHandle::default(),
 728            assistant_navigation_menu: None,
 729            width: None,
 730            height: None,
 731            zoomed: false,
 732            pending_serialization: None,
 733            onboarding,
 734        }
 735    }
 736
 737    pub fn toggle_focus(
 738        workspace: &mut Workspace,
 739        _: &ToggleFocus,
 740        window: &mut Window,
 741        cx: &mut Context<Workspace>,
 742    ) {
 743        if workspace
 744            .panel::<Self>(cx)
 745            .is_some_and(|panel| panel.read(cx).enabled(cx))
 746            && !DisableAiSettings::get_global(cx).disable_ai
 747        {
 748            workspace.toggle_panel_focus::<Self>(window, cx);
 749        }
 750    }
 751
 752    pub(crate) fn local_timezone(&self) -> UtcOffset {
 753        self.local_timezone
 754    }
 755
 756    pub(crate) fn prompt_store(&self) -> &Option<Entity<PromptStore>> {
 757        &self.prompt_store
 758    }
 759
 760    pub(crate) fn inline_assist_context_store(&self) -> &Entity<ContextStore> {
 761        &self.inline_assist_context_store
 762    }
 763
 764    pub(crate) fn thread_store(&self) -> &Entity<ThreadStore> {
 765        &self.thread_store
 766    }
 767
 768    pub(crate) fn text_thread_store(&self) -> &Entity<TextThreadStore> {
 769        &self.context_store
 770    }
 771
 772    fn cancel(&mut self, _: &editor::actions::Cancel, window: &mut Window, cx: &mut Context<Self>) {
 773        match &self.active_view {
 774            ActiveView::Thread { thread, .. } => {
 775                thread.update(cx, |thread, cx| thread.cancel_last_completion(window, cx));
 776            }
 777            ActiveView::ExternalAgentThread { thread_view, .. } => {
 778                thread_view.update(cx, |thread_element, cx| thread_element.cancel(cx));
 779            }
 780            ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {}
 781        }
 782    }
 783
 784    fn active_message_editor(&self) -> Option<&Entity<MessageEditor>> {
 785        match &self.active_view {
 786            ActiveView::Thread { message_editor, .. } => Some(message_editor),
 787            ActiveView::ExternalAgentThread { .. }
 788            | ActiveView::TextThread { .. }
 789            | ActiveView::History
 790            | ActiveView::Configuration => None,
 791        }
 792    }
 793
 794    fn new_thread(&mut self, action: &NewThread, window: &mut Window, cx: &mut Context<Self>) {
 795        // Preserve chat box text when using creating new thread
 796        let preserved_text = self
 797            .active_message_editor()
 798            .map(|editor| editor.read(cx).get_text(cx).trim().to_string());
 799
 800        let thread = self
 801            .thread_store
 802            .update(cx, |this, cx| this.create_thread(cx));
 803
 804        let context_store = cx.new(|_cx| {
 805            ContextStore::new(
 806                self.project.downgrade(),
 807                Some(self.thread_store.downgrade()),
 808            )
 809        });
 810
 811        if let Some(other_thread_id) = action.from_thread_id.clone() {
 812            let other_thread_task = self.thread_store.update(cx, |this, cx| {
 813                this.open_thread(&other_thread_id, window, cx)
 814            });
 815
 816            cx.spawn({
 817                let context_store = context_store.clone();
 818
 819                async move |_panel, cx| {
 820                    let other_thread = other_thread_task.await?;
 821
 822                    context_store.update(cx, |this, cx| {
 823                        this.add_thread(other_thread, false, cx);
 824                    })?;
 825                    anyhow::Ok(())
 826                }
 827            })
 828            .detach_and_log_err(cx);
 829        }
 830
 831        let active_thread = cx.new(|cx| {
 832            ActiveThread::new(
 833                thread.clone(),
 834                self.thread_store.clone(),
 835                self.context_store.clone(),
 836                context_store.clone(),
 837                self.language_registry.clone(),
 838                self.workspace.clone(),
 839                window,
 840                cx,
 841            )
 842        });
 843
 844        let message_editor = cx.new(|cx| {
 845            MessageEditor::new(
 846                self.fs.clone(),
 847                self.workspace.clone(),
 848                context_store.clone(),
 849                self.prompt_store.clone(),
 850                self.thread_store.downgrade(),
 851                self.context_store.downgrade(),
 852                Some(self.history_store.downgrade()),
 853                thread.clone(),
 854                window,
 855                cx,
 856            )
 857        });
 858
 859        if let Some(text) = preserved_text {
 860            message_editor.update(cx, |editor, cx| {
 861                editor.set_text(text, window, cx);
 862            });
 863        }
 864
 865        message_editor.focus_handle(cx).focus(window);
 866
 867        let thread_view = ActiveView::thread(active_thread.clone(), message_editor, window, cx);
 868        self.set_active_view(thread_view, window, cx);
 869
 870        AgentDiff::set_active_thread(&self.workspace, thread.clone(), window, cx);
 871    }
 872
 873    fn new_prompt_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 874        let context = self
 875            .context_store
 876            .update(cx, |context_store, cx| context_store.create(cx));
 877        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
 878            .log_err()
 879            .flatten();
 880
 881        let context_editor = cx.new(|cx| {
 882            let mut editor = TextThreadEditor::for_context(
 883                context,
 884                self.fs.clone(),
 885                self.workspace.clone(),
 886                self.project.clone(),
 887                lsp_adapter_delegate,
 888                window,
 889                cx,
 890            );
 891            editor.insert_default_prompt(window, cx);
 892            editor
 893        });
 894
 895        self.set_active_view(
 896            ActiveView::prompt_editor(
 897                context_editor.clone(),
 898                self.history_store.clone(),
 899                self.language_registry.clone(),
 900                window,
 901                cx,
 902            ),
 903            window,
 904            cx,
 905        );
 906        context_editor.focus_handle(cx).focus(window);
 907    }
 908
 909    fn new_external_thread(
 910        &mut self,
 911        agent_choice: Option<crate::ExternalAgent>,
 912        window: &mut Window,
 913        cx: &mut Context<Self>,
 914    ) {
 915        let workspace = self.workspace.clone();
 916        let project = self.project.clone();
 917        let message_history = self.acp_message_history.clone();
 918        let fs = self.fs.clone();
 919
 920        const LAST_USED_EXTERNAL_AGENT_KEY: &str = "agent_panel__last_used_external_agent";
 921
 922        #[derive(Default, Serialize, Deserialize)]
 923        struct LastUsedExternalAgent {
 924            agent: crate::ExternalAgent,
 925        }
 926
 927        cx.spawn_in(window, async move |this, cx| {
 928            let server: Rc<dyn AgentServer> = match agent_choice {
 929                Some(agent) => {
 930                    cx.background_spawn(async move {
 931                        if let Some(serialized) =
 932                            serde_json::to_string(&LastUsedExternalAgent { agent }).log_err()
 933                        {
 934                            KEY_VALUE_STORE
 935                                .write_kvp(LAST_USED_EXTERNAL_AGENT_KEY.to_string(), serialized)
 936                                .await
 937                                .log_err();
 938                        }
 939                    })
 940                    .detach();
 941
 942                    agent.server(fs)
 943                }
 944                None => cx
 945                    .background_spawn(async move {
 946                        KEY_VALUE_STORE.read_kvp(LAST_USED_EXTERNAL_AGENT_KEY)
 947                    })
 948                    .await
 949                    .log_err()
 950                    .flatten()
 951                    .and_then(|value| {
 952                        serde_json::from_str::<LastUsedExternalAgent>(&value).log_err()
 953                    })
 954                    .unwrap_or_default()
 955                    .agent
 956                    .server(fs),
 957            };
 958
 959            this.update_in(cx, |this, window, cx| {
 960                let thread_view = cx.new(|cx| {
 961                    crate::acp::AcpThreadView::new(
 962                        server,
 963                        workspace.clone(),
 964                        project,
 965                        message_history,
 966                        window,
 967                        cx,
 968                    )
 969                });
 970
 971                this.set_active_view(ActiveView::ExternalAgentThread { thread_view }, window, cx);
 972            })
 973        })
 974        .detach_and_log_err(cx);
 975    }
 976
 977    fn deploy_rules_library(
 978        &mut self,
 979        action: &OpenRulesLibrary,
 980        _window: &mut Window,
 981        cx: &mut Context<Self>,
 982    ) {
 983        open_rules_library(
 984            self.language_registry.clone(),
 985            Box::new(PromptLibraryInlineAssist::new(self.workspace.clone())),
 986            Rc::new(|| {
 987                Rc::new(SlashCommandCompletionProvider::new(
 988                    Arc::new(SlashCommandWorkingSet::default()),
 989                    None,
 990                    None,
 991                ))
 992            }),
 993            action
 994                .prompt_to_select
 995                .map(|uuid| UserPromptId(uuid).into()),
 996            cx,
 997        )
 998        .detach_and_log_err(cx);
 999    }
1000
1001    fn open_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1002        if matches!(self.active_view, ActiveView::History) {
1003            if let Some(previous_view) = self.previous_view.take() {
1004                self.set_active_view(previous_view, window, cx);
1005            }
1006        } else {
1007            self.thread_store
1008                .update(cx, |thread_store, cx| thread_store.reload(cx))
1009                .detach_and_log_err(cx);
1010            self.set_active_view(ActiveView::History, window, cx);
1011        }
1012        cx.notify();
1013    }
1014
1015    pub(crate) fn open_saved_prompt_editor(
1016        &mut self,
1017        path: Arc<Path>,
1018        window: &mut Window,
1019        cx: &mut Context<Self>,
1020    ) -> Task<Result<()>> {
1021        let context = self
1022            .context_store
1023            .update(cx, |store, cx| store.open_local_context(path, cx));
1024        cx.spawn_in(window, async move |this, cx| {
1025            let context = context.await?;
1026            this.update_in(cx, |this, window, cx| {
1027                this.open_prompt_editor(context, window, cx);
1028            })
1029        })
1030    }
1031
1032    pub(crate) fn open_prompt_editor(
1033        &mut self,
1034        context: Entity<AssistantContext>,
1035        window: &mut Window,
1036        cx: &mut Context<Self>,
1037    ) {
1038        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project.clone(), cx)
1039            .log_err()
1040            .flatten();
1041        let editor = cx.new(|cx| {
1042            TextThreadEditor::for_context(
1043                context,
1044                self.fs.clone(),
1045                self.workspace.clone(),
1046                self.project.clone(),
1047                lsp_adapter_delegate,
1048                window,
1049                cx,
1050            )
1051        });
1052        self.set_active_view(
1053            ActiveView::prompt_editor(
1054                editor.clone(),
1055                self.history_store.clone(),
1056                self.language_registry.clone(),
1057                window,
1058                cx,
1059            ),
1060            window,
1061            cx,
1062        );
1063    }
1064
1065    pub(crate) fn open_thread_by_id(
1066        &mut self,
1067        thread_id: &ThreadId,
1068        window: &mut Window,
1069        cx: &mut Context<Self>,
1070    ) -> Task<Result<()>> {
1071        let open_thread_task = self
1072            .thread_store
1073            .update(cx, |this, cx| this.open_thread(thread_id, window, cx));
1074        cx.spawn_in(window, async move |this, cx| {
1075            let thread = open_thread_task.await?;
1076            this.update_in(cx, |this, window, cx| {
1077                this.open_thread(thread, window, cx);
1078                anyhow::Ok(())
1079            })??;
1080            Ok(())
1081        })
1082    }
1083
1084    pub(crate) fn open_thread(
1085        &mut self,
1086        thread: Entity<Thread>,
1087        window: &mut Window,
1088        cx: &mut Context<Self>,
1089    ) {
1090        let context_store = cx.new(|_cx| {
1091            ContextStore::new(
1092                self.project.downgrade(),
1093                Some(self.thread_store.downgrade()),
1094            )
1095        });
1096
1097        let active_thread = cx.new(|cx| {
1098            ActiveThread::new(
1099                thread.clone(),
1100                self.thread_store.clone(),
1101                self.context_store.clone(),
1102                context_store.clone(),
1103                self.language_registry.clone(),
1104                self.workspace.clone(),
1105                window,
1106                cx,
1107            )
1108        });
1109
1110        let message_editor = cx.new(|cx| {
1111            MessageEditor::new(
1112                self.fs.clone(),
1113                self.workspace.clone(),
1114                context_store,
1115                self.prompt_store.clone(),
1116                self.thread_store.downgrade(),
1117                self.context_store.downgrade(),
1118                Some(self.history_store.downgrade()),
1119                thread.clone(),
1120                window,
1121                cx,
1122            )
1123        });
1124        message_editor.focus_handle(cx).focus(window);
1125
1126        let thread_view = ActiveView::thread(active_thread.clone(), message_editor, window, cx);
1127        self.set_active_view(thread_view, window, cx);
1128        AgentDiff::set_active_thread(&self.workspace, thread.clone(), window, cx);
1129    }
1130
1131    pub fn go_back(&mut self, _: &workspace::GoBack, window: &mut Window, cx: &mut Context<Self>) {
1132        match self.active_view {
1133            ActiveView::Configuration | ActiveView::History => {
1134                if let Some(previous_view) = self.previous_view.take() {
1135                    self.active_view = previous_view;
1136
1137                    match &self.active_view {
1138                        ActiveView::Thread { message_editor, .. } => {
1139                            message_editor.focus_handle(cx).focus(window);
1140                        }
1141                        ActiveView::ExternalAgentThread { thread_view } => {
1142                            thread_view.focus_handle(cx).focus(window);
1143                        }
1144                        ActiveView::TextThread { context_editor, .. } => {
1145                            context_editor.focus_handle(cx).focus(window);
1146                        }
1147                        ActiveView::History | ActiveView::Configuration => {}
1148                    }
1149                }
1150                cx.notify();
1151            }
1152            _ => {}
1153        }
1154    }
1155
1156    pub fn toggle_navigation_menu(
1157        &mut self,
1158        _: &ToggleNavigationMenu,
1159        window: &mut Window,
1160        cx: &mut Context<Self>,
1161    ) {
1162        self.assistant_navigation_menu_handle.toggle(window, cx);
1163    }
1164
1165    pub fn toggle_options_menu(
1166        &mut self,
1167        _: &ToggleOptionsMenu,
1168        window: &mut Window,
1169        cx: &mut Context<Self>,
1170    ) {
1171        self.agent_panel_menu_handle.toggle(window, cx);
1172    }
1173
1174    pub fn increase_font_size(
1175        &mut self,
1176        action: &IncreaseBufferFontSize,
1177        _: &mut Window,
1178        cx: &mut Context<Self>,
1179    ) {
1180        self.handle_font_size_action(action.persist, px(1.0), cx);
1181    }
1182
1183    pub fn decrease_font_size(
1184        &mut self,
1185        action: &DecreaseBufferFontSize,
1186        _: &mut Window,
1187        cx: &mut Context<Self>,
1188    ) {
1189        self.handle_font_size_action(action.persist, px(-1.0), cx);
1190    }
1191
1192    fn handle_font_size_action(&mut self, persist: bool, delta: Pixels, cx: &mut Context<Self>) {
1193        match self.active_view.which_font_size_used() {
1194            WhichFontSize::AgentFont => {
1195                if persist {
1196                    update_settings_file::<ThemeSettings>(
1197                        self.fs.clone(),
1198                        cx,
1199                        move |settings, cx| {
1200                            let agent_font_size =
1201                                ThemeSettings::get_global(cx).agent_font_size(cx) + delta;
1202                            let _ = settings
1203                                .agent_font_size
1204                                .insert(theme::clamp_font_size(agent_font_size).0);
1205                        },
1206                    );
1207                } else {
1208                    theme::adjust_agent_font_size(cx, |size| {
1209                        *size += delta;
1210                    });
1211                }
1212            }
1213            WhichFontSize::BufferFont => {
1214                // Prompt editor uses the buffer font size, so allow the action to propagate to the
1215                // default handler that changes that font size.
1216                cx.propagate();
1217            }
1218            WhichFontSize::None => {}
1219        }
1220    }
1221
1222    pub fn reset_font_size(
1223        &mut self,
1224        action: &ResetBufferFontSize,
1225        _: &mut Window,
1226        cx: &mut Context<Self>,
1227    ) {
1228        if action.persist {
1229            update_settings_file::<ThemeSettings>(self.fs.clone(), cx, move |settings, _| {
1230                settings.agent_font_size = None;
1231            });
1232        } else {
1233            theme::reset_agent_font_size(cx);
1234        }
1235    }
1236
1237    pub fn toggle_zoom(&mut self, _: &ToggleZoom, window: &mut Window, cx: &mut Context<Self>) {
1238        if self.zoomed {
1239            cx.emit(PanelEvent::ZoomOut);
1240        } else {
1241            if !self.focus_handle(cx).contains_focused(window, cx) {
1242                cx.focus_self(window);
1243            }
1244            cx.emit(PanelEvent::ZoomIn);
1245        }
1246    }
1247
1248    pub fn open_agent_diff(
1249        &mut self,
1250        _: &OpenAgentDiff,
1251        window: &mut Window,
1252        cx: &mut Context<Self>,
1253    ) {
1254        match &self.active_view {
1255            ActiveView::Thread { thread, .. } => {
1256                let thread = thread.read(cx).thread().clone();
1257                self.workspace
1258                    .update(cx, |workspace, cx| {
1259                        AgentDiffPane::deploy_in_workspace(
1260                            AgentDiffThread::Native(thread),
1261                            workspace,
1262                            window,
1263                            cx,
1264                        )
1265                    })
1266                    .log_err();
1267            }
1268            ActiveView::ExternalAgentThread { .. }
1269            | ActiveView::TextThread { .. }
1270            | ActiveView::History
1271            | ActiveView::Configuration => {}
1272        }
1273    }
1274
1275    pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1276        let context_server_store = self.project.read(cx).context_server_store();
1277        let tools = self.thread_store.read(cx).tools();
1278        let fs = self.fs.clone();
1279
1280        self.set_active_view(ActiveView::Configuration, window, cx);
1281        self.configuration = Some(cx.new(|cx| {
1282            AgentConfiguration::new(
1283                fs,
1284                context_server_store,
1285                tools,
1286                self.language_registry.clone(),
1287                self.workspace.clone(),
1288                window,
1289                cx,
1290            )
1291        }));
1292
1293        if let Some(configuration) = self.configuration.as_ref() {
1294            self.configuration_subscription = Some(cx.subscribe_in(
1295                configuration,
1296                window,
1297                Self::handle_agent_configuration_event,
1298            ));
1299
1300            configuration.focus_handle(cx).focus(window);
1301        }
1302    }
1303
1304    pub(crate) fn open_active_thread_as_markdown(
1305        &mut self,
1306        _: &OpenActiveThreadAsMarkdown,
1307        window: &mut Window,
1308        cx: &mut Context<Self>,
1309    ) {
1310        let Some(workspace) = self.workspace.upgrade() else {
1311            return;
1312        };
1313
1314        match &self.active_view {
1315            ActiveView::Thread { thread, .. } => {
1316                active_thread::open_active_thread_as_markdown(
1317                    thread.read(cx).thread().clone(),
1318                    workspace,
1319                    window,
1320                    cx,
1321                )
1322                .detach_and_log_err(cx);
1323            }
1324            ActiveView::ExternalAgentThread { thread_view } => {
1325                thread_view
1326                    .update(cx, |thread_view, cx| {
1327                        thread_view.open_thread_as_markdown(workspace, window, cx)
1328                    })
1329                    .detach_and_log_err(cx);
1330            }
1331            ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {}
1332        }
1333    }
1334
1335    fn handle_agent_configuration_event(
1336        &mut self,
1337        _entity: &Entity<AgentConfiguration>,
1338        event: &AssistantConfigurationEvent,
1339        window: &mut Window,
1340        cx: &mut Context<Self>,
1341    ) {
1342        match event {
1343            AssistantConfigurationEvent::NewThread(provider) => {
1344                if LanguageModelRegistry::read_global(cx)
1345                    .default_model()
1346                    .map_or(true, |model| model.provider.id() != provider.id())
1347                {
1348                    if let Some(model) = provider.default_model(cx) {
1349                        update_settings_file::<AgentSettings>(
1350                            self.fs.clone(),
1351                            cx,
1352                            move |settings, _| settings.set_model(model),
1353                        );
1354                    }
1355                }
1356
1357                self.new_thread(&NewThread::default(), window, cx);
1358                if let Some((thread, model)) =
1359                    self.active_thread(cx).zip(provider.default_model(cx))
1360                {
1361                    thread.update(cx, |thread, cx| {
1362                        thread.set_configured_model(
1363                            Some(ConfiguredModel {
1364                                provider: provider.clone(),
1365                                model,
1366                            }),
1367                            cx,
1368                        );
1369                    });
1370                }
1371            }
1372        }
1373    }
1374
1375    pub(crate) fn active_thread(&self, cx: &App) -> Option<Entity<Thread>> {
1376        match &self.active_view {
1377            ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
1378            _ => None,
1379        }
1380    }
1381
1382    pub(crate) fn delete_thread(
1383        &mut self,
1384        thread_id: &ThreadId,
1385        cx: &mut Context<Self>,
1386    ) -> Task<Result<()>> {
1387        self.thread_store
1388            .update(cx, |this, cx| this.delete_thread(thread_id, cx))
1389    }
1390
1391    fn continue_conversation(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1392        let ActiveView::Thread { thread, .. } = &self.active_view else {
1393            return;
1394        };
1395
1396        let thread_state = thread.read(cx).thread().read(cx);
1397        if !thread_state.tool_use_limit_reached() {
1398            return;
1399        }
1400
1401        let model = thread_state.configured_model().map(|cm| cm.model.clone());
1402        if let Some(model) = model {
1403            thread.update(cx, |active_thread, cx| {
1404                active_thread.thread().update(cx, |thread, cx| {
1405                    thread.insert_invisible_continue_message(cx);
1406                    thread.advance_prompt_id();
1407                    thread.send_to_model(
1408                        model,
1409                        CompletionIntent::UserPrompt,
1410                        Some(window.window_handle()),
1411                        cx,
1412                    );
1413                });
1414            });
1415        } else {
1416            log::warn!("No configured model available for continuation");
1417        }
1418    }
1419
1420    fn toggle_burn_mode(
1421        &mut self,
1422        _: &ToggleBurnMode,
1423        _window: &mut Window,
1424        cx: &mut Context<Self>,
1425    ) {
1426        let ActiveView::Thread { thread, .. } = &self.active_view else {
1427            return;
1428        };
1429
1430        thread.update(cx, |active_thread, cx| {
1431            active_thread.thread().update(cx, |thread, _cx| {
1432                let current_mode = thread.completion_mode();
1433
1434                thread.set_completion_mode(match current_mode {
1435                    CompletionMode::Burn => CompletionMode::Normal,
1436                    CompletionMode::Normal => CompletionMode::Burn,
1437                });
1438            });
1439        });
1440    }
1441
1442    pub(crate) fn active_context_editor(&self) -> Option<Entity<TextThreadEditor>> {
1443        match &self.active_view {
1444            ActiveView::TextThread { context_editor, .. } => Some(context_editor.clone()),
1445            _ => None,
1446        }
1447    }
1448
1449    pub(crate) fn delete_context(
1450        &mut self,
1451        path: Arc<Path>,
1452        cx: &mut Context<Self>,
1453    ) -> Task<Result<()>> {
1454        self.context_store
1455            .update(cx, |this, cx| this.delete_local_context(path, cx))
1456    }
1457
1458    fn set_active_view(
1459        &mut self,
1460        new_view: ActiveView,
1461        window: &mut Window,
1462        cx: &mut Context<Self>,
1463    ) {
1464        let current_is_history = matches!(self.active_view, ActiveView::History);
1465        let new_is_history = matches!(new_view, ActiveView::History);
1466
1467        let current_is_config = matches!(self.active_view, ActiveView::Configuration);
1468        let new_is_config = matches!(new_view, ActiveView::Configuration);
1469
1470        let current_is_special = current_is_history || current_is_config;
1471        let new_is_special = new_is_history || new_is_config;
1472
1473        match &self.active_view {
1474            ActiveView::Thread { thread, .. } => {
1475                let thread = thread.read(cx);
1476                if thread.is_empty() {
1477                    let id = thread.thread().read(cx).id().clone();
1478                    self.history_store.update(cx, |store, cx| {
1479                        store.remove_recently_opened_thread(id, cx);
1480                    });
1481                }
1482            }
1483            _ => {}
1484        }
1485
1486        match &new_view {
1487            ActiveView::Thread { thread, .. } => self.history_store.update(cx, |store, cx| {
1488                let id = thread.read(cx).thread().read(cx).id().clone();
1489                store.push_recently_opened_entry(HistoryEntryId::Thread(id), cx);
1490            }),
1491            ActiveView::TextThread { context_editor, .. } => {
1492                self.history_store.update(cx, |store, cx| {
1493                    if let Some(path) = context_editor.read(cx).context().read(cx).path() {
1494                        store.push_recently_opened_entry(HistoryEntryId::Context(path.clone()), cx)
1495                    }
1496                })
1497            }
1498            ActiveView::ExternalAgentThread { .. } => {}
1499            ActiveView::History | ActiveView::Configuration => {}
1500        }
1501
1502        if current_is_special && !new_is_special {
1503            self.active_view = new_view;
1504        } else if !current_is_special && new_is_special {
1505            self.previous_view = Some(std::mem::replace(&mut self.active_view, new_view));
1506        } else {
1507            if !new_is_special {
1508                self.previous_view = None;
1509            }
1510            self.active_view = new_view;
1511        }
1512
1513        self.acp_message_history.borrow_mut().reset_position();
1514
1515        self.focus_handle(cx).focus(window);
1516    }
1517
1518    fn populate_recently_opened_menu_section(
1519        mut menu: ContextMenu,
1520        panel: Entity<Self>,
1521        cx: &mut Context<ContextMenu>,
1522    ) -> ContextMenu {
1523        let entries = panel
1524            .read(cx)
1525            .history_store
1526            .read(cx)
1527            .recently_opened_entries(cx);
1528
1529        if entries.is_empty() {
1530            return menu;
1531        }
1532
1533        menu = menu.header("Recently Opened");
1534
1535        for entry in entries {
1536            let title = entry.title().clone();
1537            let id = entry.id();
1538
1539            menu = menu.entry_with_end_slot_on_hover(
1540                title,
1541                None,
1542                {
1543                    let panel = panel.downgrade();
1544                    let id = id.clone();
1545                    move |window, cx| {
1546                        let id = id.clone();
1547                        panel
1548                            .update(cx, move |this, cx| match id {
1549                                HistoryEntryId::Thread(id) => this
1550                                    .open_thread_by_id(&id, window, cx)
1551                                    .detach_and_log_err(cx),
1552                                HistoryEntryId::Context(path) => this
1553                                    .open_saved_prompt_editor(path.clone(), window, cx)
1554                                    .detach_and_log_err(cx),
1555                            })
1556                            .ok();
1557                    }
1558                },
1559                IconName::Close,
1560                "Close Entry".into(),
1561                {
1562                    let panel = panel.downgrade();
1563                    let id = id.clone();
1564                    move |_window, cx| {
1565                        panel
1566                            .update(cx, |this, cx| {
1567                                this.history_store.update(cx, |history_store, cx| {
1568                                    history_store.remove_recently_opened_entry(&id, cx);
1569                                });
1570                            })
1571                            .ok();
1572                    }
1573                },
1574            );
1575        }
1576
1577        menu = menu.separator();
1578
1579        menu
1580    }
1581}
1582
1583impl Focusable for AgentPanel {
1584    fn focus_handle(&self, cx: &App) -> FocusHandle {
1585        match &self.active_view {
1586            ActiveView::Thread { message_editor, .. } => message_editor.focus_handle(cx),
1587            ActiveView::ExternalAgentThread { thread_view, .. } => thread_view.focus_handle(cx),
1588            ActiveView::History => self.history.focus_handle(cx),
1589            ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx),
1590            ActiveView::Configuration => {
1591                if let Some(configuration) = self.configuration.as_ref() {
1592                    configuration.focus_handle(cx)
1593                } else {
1594                    cx.focus_handle()
1595                }
1596            }
1597        }
1598    }
1599}
1600
1601fn agent_panel_dock_position(cx: &App) -> DockPosition {
1602    match AgentSettings::get_global(cx).dock {
1603        AgentDockPosition::Left => DockPosition::Left,
1604        AgentDockPosition::Bottom => DockPosition::Bottom,
1605        AgentDockPosition::Right => DockPosition::Right,
1606    }
1607}
1608
1609impl EventEmitter<PanelEvent> for AgentPanel {}
1610
1611impl Panel for AgentPanel {
1612    fn persistent_name() -> &'static str {
1613        "AgentPanel"
1614    }
1615
1616    fn position(&self, _window: &Window, cx: &App) -> DockPosition {
1617        agent_panel_dock_position(cx)
1618    }
1619
1620    fn position_is_valid(&self, position: DockPosition) -> bool {
1621        position != DockPosition::Bottom
1622    }
1623
1624    fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
1625        settings::update_settings_file::<AgentSettings>(self.fs.clone(), cx, move |settings, _| {
1626            let dock = match position {
1627                DockPosition::Left => AgentDockPosition::Left,
1628                DockPosition::Bottom => AgentDockPosition::Bottom,
1629                DockPosition::Right => AgentDockPosition::Right,
1630            };
1631            settings.set_dock(dock);
1632        });
1633    }
1634
1635    fn size(&self, window: &Window, cx: &App) -> Pixels {
1636        let settings = AgentSettings::get_global(cx);
1637        match self.position(window, cx) {
1638            DockPosition::Left | DockPosition::Right => {
1639                self.width.unwrap_or(settings.default_width)
1640            }
1641            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
1642        }
1643    }
1644
1645    fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
1646        match self.position(window, cx) {
1647            DockPosition::Left | DockPosition::Right => self.width = size,
1648            DockPosition::Bottom => self.height = size,
1649        }
1650        self.serialize(cx);
1651        cx.notify();
1652    }
1653
1654    fn set_active(&mut self, _active: bool, _window: &mut Window, _cx: &mut Context<Self>) {}
1655
1656    fn remote_id() -> Option<proto::PanelId> {
1657        Some(proto::PanelId::AssistantPanel)
1658    }
1659
1660    fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
1661        (self.enabled(cx) && AgentSettings::get_global(cx).button).then_some(IconName::ZedAssistant)
1662    }
1663
1664    fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
1665        Some("Agent Panel")
1666    }
1667
1668    fn toggle_action(&self) -> Box<dyn Action> {
1669        Box::new(ToggleFocus)
1670    }
1671
1672    fn activation_priority(&self) -> u32 {
1673        3
1674    }
1675
1676    fn enabled(&self, cx: &App) -> bool {
1677        DisableAiSettings::get_global(cx).disable_ai.not() && AgentSettings::get_global(cx).enabled
1678    }
1679
1680    fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
1681        self.zoomed
1682    }
1683
1684    fn set_zoomed(&mut self, zoomed: bool, _window: &mut Window, cx: &mut Context<Self>) {
1685        self.zoomed = zoomed;
1686        cx.notify();
1687    }
1688}
1689
1690impl AgentPanel {
1691    fn render_title_view(&self, _window: &mut Window, cx: &Context<Self>) -> AnyElement {
1692        const LOADING_SUMMARY_PLACEHOLDER: &str = "Loading Summary…";
1693
1694        let content = match &self.active_view {
1695            ActiveView::Thread {
1696                thread: active_thread,
1697                change_title_editor,
1698                ..
1699            } => {
1700                let state = {
1701                    let active_thread = active_thread.read(cx);
1702                    if active_thread.is_empty() {
1703                        &ThreadSummary::Pending
1704                    } else {
1705                        active_thread.summary(cx)
1706                    }
1707                };
1708
1709                match state {
1710                    ThreadSummary::Pending => Label::new(ThreadSummary::DEFAULT.clone())
1711                        .truncate()
1712                        .into_any_element(),
1713                    ThreadSummary::Generating => Label::new(LOADING_SUMMARY_PLACEHOLDER)
1714                        .truncate()
1715                        .into_any_element(),
1716                    ThreadSummary::Ready(_) => div()
1717                        .w_full()
1718                        .child(change_title_editor.clone())
1719                        .into_any_element(),
1720                    ThreadSummary::Error => h_flex()
1721                        .w_full()
1722                        .child(change_title_editor.clone())
1723                        .child(
1724                            ui::IconButton::new("retry-summary-generation", IconName::RotateCcw)
1725                                .on_click({
1726                                    let active_thread = active_thread.clone();
1727                                    move |_, _window, cx| {
1728                                        active_thread.update(cx, |thread, cx| {
1729                                            thread.regenerate_summary(cx);
1730                                        });
1731                                    }
1732                                })
1733                                .tooltip(move |_window, cx| {
1734                                    cx.new(|_| {
1735                                        Tooltip::new("Failed to generate title")
1736                                            .meta("Click to try again")
1737                                    })
1738                                    .into()
1739                                }),
1740                        )
1741                        .into_any_element(),
1742                }
1743            }
1744            ActiveView::ExternalAgentThread { thread_view } => {
1745                Label::new(thread_view.read(cx).title(cx))
1746                    .truncate()
1747                    .into_any_element()
1748            }
1749            ActiveView::TextThread {
1750                title_editor,
1751                context_editor,
1752                ..
1753            } => {
1754                let summary = context_editor.read(cx).context().read(cx).summary();
1755
1756                match summary {
1757                    ContextSummary::Pending => Label::new(ContextSummary::DEFAULT)
1758                        .truncate()
1759                        .into_any_element(),
1760                    ContextSummary::Content(summary) => {
1761                        if summary.done {
1762                            div()
1763                                .w_full()
1764                                .child(title_editor.clone())
1765                                .into_any_element()
1766                        } else {
1767                            Label::new(LOADING_SUMMARY_PLACEHOLDER)
1768                                .truncate()
1769                                .into_any_element()
1770                        }
1771                    }
1772                    ContextSummary::Error => h_flex()
1773                        .w_full()
1774                        .child(title_editor.clone())
1775                        .child(
1776                            ui::IconButton::new("retry-summary-generation", IconName::RotateCcw)
1777                                .on_click({
1778                                    let context_editor = context_editor.clone();
1779                                    move |_, _window, cx| {
1780                                        context_editor.update(cx, |context_editor, cx| {
1781                                            context_editor.regenerate_summary(cx);
1782                                        });
1783                                    }
1784                                })
1785                                .tooltip(move |_window, cx| {
1786                                    cx.new(|_| {
1787                                        Tooltip::new("Failed to generate title")
1788                                            .meta("Click to try again")
1789                                    })
1790                                    .into()
1791                                }),
1792                        )
1793                        .into_any_element(),
1794                }
1795            }
1796            ActiveView::History => Label::new("History").truncate().into_any_element(),
1797            ActiveView::Configuration => Label::new("Settings").truncate().into_any_element(),
1798        };
1799
1800        h_flex()
1801            .key_context("TitleEditor")
1802            .id("TitleEditor")
1803            .flex_grow()
1804            .w_full()
1805            .max_w_full()
1806            .overflow_x_scroll()
1807            .child(content)
1808            .into_any()
1809    }
1810
1811    fn render_toolbar(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1812        let user_store = self.user_store.read(cx);
1813        let usage = user_store.model_request_usage();
1814
1815        let account_url = zed_urls::account_url(cx);
1816
1817        let focus_handle = self.focus_handle(cx);
1818
1819        let go_back_button = div().child(
1820            IconButton::new("go-back", IconName::ArrowLeft)
1821                .icon_size(IconSize::Small)
1822                .on_click(cx.listener(|this, _, window, cx| {
1823                    this.go_back(&workspace::GoBack, window, cx);
1824                }))
1825                .tooltip({
1826                    let focus_handle = focus_handle.clone();
1827                    move |window, cx| {
1828                        Tooltip::for_action_in(
1829                            "Go Back",
1830                            &workspace::GoBack,
1831                            &focus_handle,
1832                            window,
1833                            cx,
1834                        )
1835                    }
1836                }),
1837        );
1838
1839        let recent_entries_menu = div().child(
1840            PopoverMenu::new("agent-nav-menu")
1841                .trigger_with_tooltip(
1842                    IconButton::new("agent-nav-menu", IconName::MenuAlt)
1843                        .icon_size(IconSize::Small)
1844                        .style(ui::ButtonStyle::Subtle),
1845                    {
1846                        let focus_handle = focus_handle.clone();
1847                        move |window, cx| {
1848                            Tooltip::for_action_in(
1849                                "Toggle Panel Menu",
1850                                &ToggleNavigationMenu,
1851                                &focus_handle,
1852                                window,
1853                                cx,
1854                            )
1855                        }
1856                    },
1857                )
1858                .anchor(Corner::TopLeft)
1859                .with_handle(self.assistant_navigation_menu_handle.clone())
1860                .menu({
1861                    let menu = self.assistant_navigation_menu.clone();
1862                    move |window, cx| {
1863                        if let Some(menu) = menu.as_ref() {
1864                            menu.update(cx, |_, cx| {
1865                                cx.defer_in(window, |menu, window, cx| {
1866                                    menu.rebuild(window, cx);
1867                                });
1868                            })
1869                        }
1870                        menu.clone()
1871                    }
1872                }),
1873        );
1874
1875        let full_screen_label = if self.is_zoomed(window, cx) {
1876            "Disable Full Screen"
1877        } else {
1878            "Enable Full Screen"
1879        };
1880
1881        let active_thread = match &self.active_view {
1882            ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
1883            ActiveView::ExternalAgentThread { .. }
1884            | ActiveView::TextThread { .. }
1885            | ActiveView::History
1886            | ActiveView::Configuration => None,
1887        };
1888
1889        let new_thread_menu = PopoverMenu::new("new_thread_menu")
1890            .trigger_with_tooltip(
1891                IconButton::new("new_thread_menu_btn", IconName::Plus).icon_size(IconSize::Small),
1892                Tooltip::text("New Thread…"),
1893            )
1894            .anchor(Corner::TopRight)
1895            .with_handle(self.new_thread_menu_handle.clone())
1896            .menu({
1897                let focus_handle = focus_handle.clone();
1898                move |window, cx| {
1899                    let active_thread = active_thread.clone();
1900                    Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
1901                        menu = menu
1902                            .context(focus_handle.clone())
1903                            .when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
1904                                this.header("Zed Agent")
1905                            })
1906                            .when_some(active_thread, |this, active_thread| {
1907                                let thread = active_thread.read(cx);
1908
1909                                if !thread.is_empty() {
1910                                    let thread_id = thread.id().clone();
1911                                    this.item(
1912                                        ContextMenuEntry::new("New From Summary")
1913                                            .icon(IconName::ThreadFromSummary)
1914                                            .icon_color(Color::Muted)
1915                                            .handler(move |window, cx| {
1916                                                window.dispatch_action(
1917                                                    Box::new(NewThread {
1918                                                        from_thread_id: Some(thread_id.clone()),
1919                                                    }),
1920                                                    cx,
1921                                                );
1922                                            }),
1923                                    )
1924                                } else {
1925                                    this
1926                                }
1927                            })
1928                            .item(
1929                                ContextMenuEntry::new("New Thread")
1930                                    .icon(IconName::Thread)
1931                                    .icon_color(Color::Muted)
1932                                    .action(NewThread::default().boxed_clone())
1933                                    .handler(move |window, cx| {
1934                                        window.dispatch_action(
1935                                            NewThread::default().boxed_clone(),
1936                                            cx,
1937                                        );
1938                                    }),
1939                            )
1940                            .item(
1941                                ContextMenuEntry::new("New Text Thread")
1942                                    .icon(IconName::TextThread)
1943                                    .icon_color(Color::Muted)
1944                                    .action(NewTextThread.boxed_clone())
1945                                    .handler(move |window, cx| {
1946                                        window.dispatch_action(NewTextThread.boxed_clone(), cx);
1947                                    }),
1948                            )
1949                            .when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
1950                                this.separator()
1951                                    .header("External Agents")
1952                                    .item(
1953                                        ContextMenuEntry::new("New Gemini Thread")
1954                                            .icon(IconName::AiGemini)
1955                                            .icon_color(Color::Muted)
1956                                            .handler(move |window, cx| {
1957                                                window.dispatch_action(
1958                                                    NewExternalAgentThread {
1959                                                        agent: Some(crate::ExternalAgent::Gemini),
1960                                                    }
1961                                                    .boxed_clone(),
1962                                                    cx,
1963                                                );
1964                                            }),
1965                                    )
1966                                    .item(
1967                                        ContextMenuEntry::new("New Claude Code Thread")
1968                                            .icon(IconName::AiClaude)
1969                                            .icon_color(Color::Muted)
1970                                            .handler(move |window, cx| {
1971                                                window.dispatch_action(
1972                                                    NewExternalAgentThread {
1973                                                        agent: Some(
1974                                                            crate::ExternalAgent::ClaudeCode,
1975                                                        ),
1976                                                    }
1977                                                    .boxed_clone(),
1978                                                    cx,
1979                                                );
1980                                            }),
1981                                    )
1982                                    .item(
1983                                        ContextMenuEntry::new("New Native Agent Thread")
1984                                            .icon(IconName::ZedAssistant)
1985                                            .icon_color(Color::Muted)
1986                                            .handler(move |window, cx| {
1987                                                window.dispatch_action(
1988                                                    NewExternalAgentThread {
1989                                                        agent: Some(
1990                                                            crate::ExternalAgent::NativeAgent,
1991                                                        ),
1992                                                    }
1993                                                    .boxed_clone(),
1994                                                    cx,
1995                                                );
1996                                            }),
1997                                    )
1998                            });
1999                        menu
2000                    }))
2001                }
2002            });
2003
2004        let agent_panel_menu = PopoverMenu::new("agent-options-menu")
2005            .trigger_with_tooltip(
2006                IconButton::new("agent-options-menu", IconName::Ellipsis)
2007                    .icon_size(IconSize::Small),
2008                {
2009                    let focus_handle = focus_handle.clone();
2010                    move |window, cx| {
2011                        Tooltip::for_action_in(
2012                            "Toggle Agent Menu",
2013                            &ToggleOptionsMenu,
2014                            &focus_handle,
2015                            window,
2016                            cx,
2017                        )
2018                    }
2019                },
2020            )
2021            .anchor(Corner::TopRight)
2022            .with_handle(self.agent_panel_menu_handle.clone())
2023            .menu({
2024                let focus_handle = focus_handle.clone();
2025                move |window, cx| {
2026                    Some(ContextMenu::build(window, cx, |mut menu, _window, _| {
2027                        menu = menu.context(focus_handle.clone());
2028                        if let Some(usage) = usage {
2029                            menu = menu
2030                                .header_with_link("Prompt Usage", "Manage", account_url.clone())
2031                                .custom_entry(
2032                                    move |_window, cx| {
2033                                        let used_percentage = match usage.limit {
2034                                            UsageLimit::Limited(limit) => {
2035                                                Some((usage.amount as f32 / limit as f32) * 100.)
2036                                            }
2037                                            UsageLimit::Unlimited => None,
2038                                        };
2039
2040                                        h_flex()
2041                                            .flex_1()
2042                                            .gap_1p5()
2043                                            .children(used_percentage.map(|percent| {
2044                                                ProgressBar::new("usage", percent, 100., cx)
2045                                            }))
2046                                            .child(
2047                                                Label::new(match usage.limit {
2048                                                    UsageLimit::Limited(limit) => {
2049                                                        format!("{} / {limit}", usage.amount)
2050                                                    }
2051                                                    UsageLimit::Unlimited => {
2052                                                        format!("{} / ∞", usage.amount)
2053                                                    }
2054                                                })
2055                                                .size(LabelSize::Small)
2056                                                .color(Color::Muted),
2057                                            )
2058                                            .into_any_element()
2059                                    },
2060                                    move |_, cx| cx.open_url(&zed_urls::account_url(cx)),
2061                                )
2062                                .separator()
2063                        }
2064
2065                        menu = menu
2066                            .header("MCP Servers")
2067                            .action(
2068                                "View Server Extensions",
2069                                Box::new(zed_actions::Extensions {
2070                                    category_filter: Some(
2071                                        zed_actions::ExtensionCategoryFilter::ContextServers,
2072                                    ),
2073                                    id: None,
2074                                }),
2075                            )
2076                            .action("Add Custom Server…", Box::new(AddContextServer))
2077                            .separator();
2078
2079                        menu = menu
2080                            .action("Rules…", Box::new(OpenRulesLibrary::default()))
2081                            .action("Settings", Box::new(OpenSettings))
2082                            .separator()
2083                            .action(full_screen_label, Box::new(ToggleZoom));
2084                        menu
2085                    }))
2086                }
2087            });
2088
2089        h_flex()
2090            .id("assistant-toolbar")
2091            .h(Tab::container_height(cx))
2092            .max_w_full()
2093            .flex_none()
2094            .justify_between()
2095            .gap_2()
2096            .bg(cx.theme().colors().tab_bar_background)
2097            .border_b_1()
2098            .border_color(cx.theme().colors().border)
2099            .child(
2100                h_flex()
2101                    .size_full()
2102                    .pl_1()
2103                    .gap_1()
2104                    .child(match &self.active_view {
2105                        ActiveView::History | ActiveView::Configuration => go_back_button,
2106                        _ => recent_entries_menu,
2107                    })
2108                    .child(self.render_title_view(window, cx)),
2109            )
2110            .child(
2111                h_flex()
2112                    .h_full()
2113                    .gap_2()
2114                    .children(self.render_token_count(cx))
2115                    .child(
2116                        h_flex()
2117                            .h_full()
2118                            .gap(DynamicSpacing::Base02.rems(cx))
2119                            .px(DynamicSpacing::Base08.rems(cx))
2120                            .border_l_1()
2121                            .border_color(cx.theme().colors().border)
2122                            .child(new_thread_menu)
2123                            .child(agent_panel_menu),
2124                    ),
2125            )
2126    }
2127
2128    fn render_token_count(&self, cx: &App) -> Option<AnyElement> {
2129        match &self.active_view {
2130            ActiveView::Thread {
2131                thread,
2132                message_editor,
2133                ..
2134            } => {
2135                let active_thread = thread.read(cx);
2136                let message_editor = message_editor.read(cx);
2137
2138                let editor_empty = message_editor.is_editor_fully_empty(cx);
2139
2140                if active_thread.is_empty() && editor_empty {
2141                    return None;
2142                }
2143
2144                let thread = active_thread.thread().read(cx);
2145                let is_generating = thread.is_generating();
2146                let conversation_token_usage = thread.total_token_usage()?;
2147
2148                let (total_token_usage, is_estimating) =
2149                    if let Some((editing_message_id, unsent_tokens)) =
2150                        active_thread.editing_message_id()
2151                    {
2152                        let combined = thread
2153                            .token_usage_up_to_message(editing_message_id)
2154                            .add(unsent_tokens);
2155
2156                        (combined, unsent_tokens > 0)
2157                    } else {
2158                        let unsent_tokens =
2159                            message_editor.last_estimated_token_count().unwrap_or(0);
2160                        let combined = conversation_token_usage.add(unsent_tokens);
2161
2162                        (combined, unsent_tokens > 0)
2163                    };
2164
2165                let is_waiting_to_update_token_count =
2166                    message_editor.is_waiting_to_update_token_count();
2167
2168                if total_token_usage.total == 0 {
2169                    return None;
2170                }
2171
2172                let token_color = match total_token_usage.ratio() {
2173                    TokenUsageRatio::Normal if is_estimating => Color::Default,
2174                    TokenUsageRatio::Normal => Color::Muted,
2175                    TokenUsageRatio::Warning => Color::Warning,
2176                    TokenUsageRatio::Exceeded => Color::Error,
2177                };
2178
2179                let token_count = h_flex()
2180                    .id("token-count")
2181                    .flex_shrink_0()
2182                    .gap_0p5()
2183                    .when(!is_generating && is_estimating, |parent| {
2184                        parent
2185                            .child(
2186                                h_flex()
2187                                    .mr_1()
2188                                    .size_2p5()
2189                                    .justify_center()
2190                                    .rounded_full()
2191                                    .bg(cx.theme().colors().text.opacity(0.1))
2192                                    .child(
2193                                        div().size_1().rounded_full().bg(cx.theme().colors().text),
2194                                    ),
2195                            )
2196                            .tooltip(move |window, cx| {
2197                                Tooltip::with_meta(
2198                                    "Estimated New Token Count",
2199                                    None,
2200                                    format!(
2201                                        "Current Conversation Tokens: {}",
2202                                        humanize_token_count(conversation_token_usage.total)
2203                                    ),
2204                                    window,
2205                                    cx,
2206                                )
2207                            })
2208                    })
2209                    .child(
2210                        Label::new(humanize_token_count(total_token_usage.total))
2211                            .size(LabelSize::Small)
2212                            .color(token_color)
2213                            .map(|label| {
2214                                if is_generating || is_waiting_to_update_token_count {
2215                                    label
2216                                        .with_animation(
2217                                            "used-tokens-label",
2218                                            Animation::new(Duration::from_secs(2))
2219                                                .repeat()
2220                                                .with_easing(pulsating_between(0.6, 1.)),
2221                                            |label, delta| label.alpha(delta),
2222                                        )
2223                                        .into_any()
2224                                } else {
2225                                    label.into_any_element()
2226                                }
2227                            }),
2228                    )
2229                    .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
2230                    .child(
2231                        Label::new(humanize_token_count(total_token_usage.max))
2232                            .size(LabelSize::Small)
2233                            .color(Color::Muted),
2234                    )
2235                    .into_any();
2236
2237                Some(token_count)
2238            }
2239            ActiveView::TextThread { context_editor, .. } => {
2240                let element = render_remaining_tokens(context_editor, cx)?;
2241
2242                Some(element.into_any_element())
2243            }
2244            ActiveView::ExternalAgentThread { .. }
2245            | ActiveView::History
2246            | ActiveView::Configuration => {
2247                return None;
2248            }
2249        }
2250    }
2251
2252    fn should_render_trial_end_upsell(&self, cx: &mut Context<Self>) -> bool {
2253        if TrialEndUpsell::dismissed() {
2254            return false;
2255        }
2256
2257        match &self.active_view {
2258            ActiveView::Thread { thread, .. } => {
2259                if thread
2260                    .read(cx)
2261                    .thread()
2262                    .read(cx)
2263                    .configured_model()
2264                    .map_or(false, |model| {
2265                        model.provider.id() != language_model::ZED_CLOUD_PROVIDER_ID
2266                    })
2267                {
2268                    return false;
2269                }
2270            }
2271            ActiveView::TextThread { .. } => {
2272                if LanguageModelRegistry::global(cx)
2273                    .read(cx)
2274                    .default_model()
2275                    .map_or(false, |model| {
2276                        model.provider.id() != language_model::ZED_CLOUD_PROVIDER_ID
2277                    })
2278                {
2279                    return false;
2280                }
2281            }
2282            ActiveView::ExternalAgentThread { .. }
2283            | ActiveView::History
2284            | ActiveView::Configuration => return false,
2285        }
2286
2287        let plan = self.user_store.read(cx).plan();
2288        let has_previous_trial = self.user_store.read(cx).trial_started_at().is_some();
2289
2290        matches!(plan, Some(Plan::ZedFree)) && has_previous_trial
2291    }
2292
2293    fn should_render_onboarding(&self, cx: &mut Context<Self>) -> bool {
2294        if OnboardingUpsell::dismissed() {
2295            return false;
2296        }
2297
2298        match &self.active_view {
2299            ActiveView::Thread { .. } | ActiveView::TextThread { .. } => {
2300                let history_is_empty = self
2301                    .history_store
2302                    .update(cx, |store, cx| store.recent_entries(1, cx).is_empty());
2303
2304                let has_configured_non_zed_providers = LanguageModelRegistry::read_global(cx)
2305                    .providers()
2306                    .iter()
2307                    .any(|provider| {
2308                        provider.is_authenticated(cx)
2309                            && provider.id() != language_model::ZED_CLOUD_PROVIDER_ID
2310                    });
2311
2312                history_is_empty || !has_configured_non_zed_providers
2313            }
2314            ActiveView::ExternalAgentThread { .. }
2315            | ActiveView::History
2316            | ActiveView::Configuration => false,
2317        }
2318    }
2319
2320    fn render_onboarding(
2321        &self,
2322        _window: &mut Window,
2323        cx: &mut Context<Self>,
2324    ) -> Option<impl IntoElement> {
2325        if !self.should_render_onboarding(cx) {
2326            return None;
2327        }
2328
2329        let thread_view = matches!(&self.active_view, ActiveView::Thread { .. });
2330        let text_thread_view = matches!(&self.active_view, ActiveView::TextThread { .. });
2331
2332        Some(
2333            div()
2334                .when(thread_view, |this| {
2335                    this.size_full().bg(cx.theme().colors().panel_background)
2336                })
2337                .when(text_thread_view, |this| {
2338                    this.bg(cx.theme().colors().editor_background)
2339                })
2340                .child(self.onboarding.clone()),
2341        )
2342    }
2343
2344    fn render_backdrop(&self, cx: &mut Context<Self>) -> impl IntoElement {
2345        div()
2346            .size_full()
2347            .absolute()
2348            .inset_0()
2349            .bg(cx.theme().colors().panel_background)
2350            .opacity(0.8)
2351            .block_mouse_except_scroll()
2352    }
2353
2354    fn render_trial_end_upsell(
2355        &self,
2356        _window: &mut Window,
2357        cx: &mut Context<Self>,
2358    ) -> Option<impl IntoElement> {
2359        if !self.should_render_trial_end_upsell(cx) {
2360            return None;
2361        }
2362
2363        Some(
2364            v_flex()
2365                .absolute()
2366                .inset_0()
2367                .size_full()
2368                .bg(cx.theme().colors().panel_background)
2369                .opacity(0.85)
2370                .block_mouse_except_scroll()
2371                .child(EndTrialUpsell::new(Arc::new({
2372                    let this = cx.entity();
2373                    move |_, cx| {
2374                        this.update(cx, |_this, cx| {
2375                            TrialEndUpsell::set_dismissed(true, cx);
2376                            cx.notify();
2377                        });
2378                    }
2379                }))),
2380        )
2381    }
2382
2383    fn render_empty_state_section_header(
2384        &self,
2385        label: impl Into<SharedString>,
2386        action_slot: Option<AnyElement>,
2387        cx: &mut Context<Self>,
2388    ) -> impl IntoElement {
2389        h_flex()
2390            .mt_2()
2391            .pl_1p5()
2392            .pb_1()
2393            .w_full()
2394            .justify_between()
2395            .border_b_1()
2396            .border_color(cx.theme().colors().border_variant)
2397            .child(
2398                Label::new(label.into())
2399                    .size(LabelSize::Small)
2400                    .color(Color::Muted),
2401            )
2402            .children(action_slot)
2403    }
2404
2405    fn render_thread_empty_state(
2406        &self,
2407        window: &mut Window,
2408        cx: &mut Context<Self>,
2409    ) -> impl IntoElement {
2410        let recent_history = self
2411            .history_store
2412            .update(cx, |this, cx| this.recent_entries(6, cx));
2413
2414        let model_registry = LanguageModelRegistry::read_global(cx);
2415
2416        let configuration_error =
2417            model_registry.configuration_error(model_registry.default_model(), cx);
2418
2419        let no_error = configuration_error.is_none();
2420        let focus_handle = self.focus_handle(cx);
2421
2422        v_flex()
2423            .size_full()
2424            .bg(cx.theme().colors().panel_background)
2425            .when(recent_history.is_empty(), |this| {
2426                this.child(
2427                    v_flex()
2428                        .size_full()
2429                        .mx_auto()
2430                        .justify_center()
2431                        .items_center()
2432                        .gap_1()
2433                        .child(h_flex().child(Headline::new("Welcome to the Agent Panel")))
2434                        .when(no_error, |parent| {
2435                            parent
2436                                .child(h_flex().child(
2437                                    Label::new("Ask and build anything.").color(Color::Muted),
2438                                ))
2439                                .child(
2440                                    v_flex()
2441                                        .mt_2()
2442                                        .gap_1()
2443                                        .max_w_48()
2444                                        .child(
2445                                            Button::new("context", "Add Context")
2446                                                .label_size(LabelSize::Small)
2447                                                .icon(IconName::FileCode)
2448                                                .icon_position(IconPosition::Start)
2449                                                .icon_size(IconSize::Small)
2450                                                .icon_color(Color::Muted)
2451                                                .full_width()
2452                                                .key_binding(KeyBinding::for_action_in(
2453                                                    &ToggleContextPicker,
2454                                                    &focus_handle,
2455                                                    window,
2456                                                    cx,
2457                                                ))
2458                                                .on_click(|_event, window, cx| {
2459                                                    window.dispatch_action(
2460                                                        ToggleContextPicker.boxed_clone(),
2461                                                        cx,
2462                                                    )
2463                                                }),
2464                                        )
2465                                        .child(
2466                                            Button::new("mode", "Switch Model")
2467                                                .label_size(LabelSize::Small)
2468                                                .icon(IconName::DatabaseZap)
2469                                                .icon_position(IconPosition::Start)
2470                                                .icon_size(IconSize::Small)
2471                                                .icon_color(Color::Muted)
2472                                                .full_width()
2473                                                .key_binding(KeyBinding::for_action_in(
2474                                                    &ToggleModelSelector,
2475                                                    &focus_handle,
2476                                                    window,
2477                                                    cx,
2478                                                ))
2479                                                .on_click(|_event, window, cx| {
2480                                                    window.dispatch_action(
2481                                                        ToggleModelSelector.boxed_clone(),
2482                                                        cx,
2483                                                    )
2484                                                }),
2485                                        )
2486                                        .child(
2487                                            Button::new("settings", "View Settings")
2488                                                .label_size(LabelSize::Small)
2489                                                .icon(IconName::Settings)
2490                                                .icon_position(IconPosition::Start)
2491                                                .icon_size(IconSize::Small)
2492                                                .icon_color(Color::Muted)
2493                                                .full_width()
2494                                                .key_binding(KeyBinding::for_action_in(
2495                                                    &OpenSettings,
2496                                                    &focus_handle,
2497                                                    window,
2498                                                    cx,
2499                                                ))
2500                                                .on_click(|_event, window, cx| {
2501                                                    window.dispatch_action(
2502                                                        OpenSettings.boxed_clone(),
2503                                                        cx,
2504                                                    )
2505                                                }),
2506                                        ),
2507                                )
2508                        })
2509                        .when_some(configuration_error.as_ref(), |this, err| {
2510                            this.child(self.render_configuration_error(
2511                                err,
2512                                &focus_handle,
2513                                window,
2514                                cx,
2515                            ))
2516                        }),
2517                )
2518            })
2519            .when(!recent_history.is_empty(), |parent| {
2520                let focus_handle = focus_handle.clone();
2521                parent
2522                    .overflow_hidden()
2523                    .p_1p5()
2524                    .justify_end()
2525                    .gap_1()
2526                    .child(
2527                        self.render_empty_state_section_header(
2528                            "Recent",
2529                            Some(
2530                                Button::new("view-history", "View All")
2531                                    .style(ButtonStyle::Subtle)
2532                                    .label_size(LabelSize::Small)
2533                                    .key_binding(
2534                                        KeyBinding::for_action_in(
2535                                            &OpenHistory,
2536                                            &self.focus_handle(cx),
2537                                            window,
2538                                            cx,
2539                                        )
2540                                        .map(|kb| kb.size(rems_from_px(12.))),
2541                                    )
2542                                    .on_click(move |_event, window, cx| {
2543                                        window.dispatch_action(OpenHistory.boxed_clone(), cx);
2544                                    })
2545                                    .into_any_element(),
2546                            ),
2547                            cx,
2548                        ),
2549                    )
2550                    .child(
2551                        v_flex()
2552                            .gap_1()
2553                            .children(recent_history.into_iter().enumerate().map(
2554                                |(index, entry)| {
2555                                    // TODO: Add keyboard navigation.
2556                                    let is_hovered =
2557                                        self.hovered_recent_history_item == Some(index);
2558                                    HistoryEntryElement::new(entry.clone(), cx.entity().downgrade())
2559                                        .hovered(is_hovered)
2560                                        .on_hover(cx.listener(
2561                                            move |this, is_hovered, _window, cx| {
2562                                                if *is_hovered {
2563                                                    this.hovered_recent_history_item = Some(index);
2564                                                } else if this.hovered_recent_history_item
2565                                                    == Some(index)
2566                                                {
2567                                                    this.hovered_recent_history_item = None;
2568                                                }
2569                                                cx.notify();
2570                                            },
2571                                        ))
2572                                        .into_any_element()
2573                                },
2574                            )),
2575                    )
2576                    .child(self.render_empty_state_section_header("Start", None, cx))
2577                    .child(
2578                        v_flex()
2579                            .p_1()
2580                            .gap_2()
2581                            .child(
2582                                h_flex()
2583                                    .w_full()
2584                                    .gap_2()
2585                                    .child(
2586                                        NewThreadButton::new(
2587                                            "new-thread-btn",
2588                                            "New Thread",
2589                                            IconName::Thread,
2590                                        )
2591                                        .keybinding(KeyBinding::for_action_in(
2592                                            &NewThread::default(),
2593                                            &self.focus_handle(cx),
2594                                            window,
2595                                            cx,
2596                                        ))
2597                                        .on_click(
2598                                            |window, cx| {
2599                                                window.dispatch_action(
2600                                                    NewThread::default().boxed_clone(),
2601                                                    cx,
2602                                                )
2603                                            },
2604                                        ),
2605                                    )
2606                                    .child(
2607                                        NewThreadButton::new(
2608                                            "new-text-thread-btn",
2609                                            "New Text Thread",
2610                                            IconName::TextThread,
2611                                        )
2612                                        .keybinding(KeyBinding::for_action_in(
2613                                            &NewTextThread,
2614                                            &self.focus_handle(cx),
2615                                            window,
2616                                            cx,
2617                                        ))
2618                                        .on_click(
2619                                            |window, cx| {
2620                                                window.dispatch_action(Box::new(NewTextThread), cx)
2621                                            },
2622                                        ),
2623                                    ),
2624                            )
2625                            .when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
2626                                this.child(
2627                                    h_flex()
2628                                        .w_full()
2629                                        .gap_2()
2630                                        .child(
2631                                            NewThreadButton::new(
2632                                                "new-gemini-thread-btn",
2633                                                "New Gemini Thread",
2634                                                IconName::AiGemini,
2635                                            )
2636                                            // .keybinding(KeyBinding::for_action_in(
2637                                            //     &OpenHistory,
2638                                            //     &self.focus_handle(cx),
2639                                            //     window,
2640                                            //     cx,
2641                                            // ))
2642                                            .on_click(
2643                                                |window, cx| {
2644                                                    window.dispatch_action(
2645                                                        Box::new(NewExternalAgentThread {
2646                                                            agent: Some(
2647                                                                crate::ExternalAgent::Gemini,
2648                                                            ),
2649                                                        }),
2650                                                        cx,
2651                                                    )
2652                                                },
2653                                            ),
2654                                        )
2655                                        .child(
2656                                            NewThreadButton::new(
2657                                                "new-claude-thread-btn",
2658                                                "New Claude Code Thread",
2659                                                IconName::AiClaude,
2660                                            )
2661                                            // .keybinding(KeyBinding::for_action_in(
2662                                            //     &OpenHistory,
2663                                            //     &self.focus_handle(cx),
2664                                            //     window,
2665                                            //     cx,
2666                                            // ))
2667                                            .on_click(
2668                                                |window, cx| {
2669                                                    window.dispatch_action(
2670                                                        Box::new(NewExternalAgentThread {
2671                                                            agent: Some(
2672                                                                crate::ExternalAgent::ClaudeCode,
2673                                                            ),
2674                                                        }),
2675                                                        cx,
2676                                                    )
2677                                                },
2678                                            ),
2679                                        )
2680                                        .child(
2681                                            NewThreadButton::new(
2682                                                "new-native-agent-thread-btn",
2683                                                "New Native Agent Thread",
2684                                                IconName::ZedAssistant,
2685                                            )
2686                                            // .keybinding(KeyBinding::for_action_in(
2687                                            //     &OpenHistory,
2688                                            //     &self.focus_handle(cx),
2689                                            //     window,
2690                                            //     cx,
2691                                            // ))
2692                                            .on_click(
2693                                                |window, cx| {
2694                                                    window.dispatch_action(
2695                                                        Box::new(NewExternalAgentThread {
2696                                                            agent: Some(
2697                                                                crate::ExternalAgent::NativeAgent,
2698                                                            ),
2699                                                        }),
2700                                                        cx,
2701                                                    )
2702                                                },
2703                                            ),
2704                                        ),
2705                                )
2706                            }),
2707                    )
2708                    .when_some(configuration_error.as_ref(), |this, err| {
2709                        this.child(self.render_configuration_error(err, &focus_handle, window, cx))
2710                    })
2711            })
2712    }
2713
2714    fn render_configuration_error(
2715        &self,
2716        configuration_error: &ConfigurationError,
2717        focus_handle: &FocusHandle,
2718        window: &mut Window,
2719        cx: &mut App,
2720    ) -> impl IntoElement {
2721        match configuration_error {
2722            ConfigurationError::ModelNotFound
2723            | ConfigurationError::ProviderNotAuthenticated(_)
2724            | ConfigurationError::NoProvider => Banner::new()
2725                .severity(ui::Severity::Warning)
2726                .child(Label::new(configuration_error.to_string()))
2727                .action_slot(
2728                    Button::new("settings", "Configure Provider")
2729                        .style(ButtonStyle::Tinted(ui::TintColor::Warning))
2730                        .label_size(LabelSize::Small)
2731                        .key_binding(
2732                            KeyBinding::for_action_in(&OpenSettings, &focus_handle, window, cx)
2733                                .map(|kb| kb.size(rems_from_px(12.))),
2734                        )
2735                        .on_click(|_event, window, cx| {
2736                            window.dispatch_action(OpenSettings.boxed_clone(), cx)
2737                        }),
2738                ),
2739            ConfigurationError::ProviderPendingTermsAcceptance(provider) => {
2740                Banner::new().severity(ui::Severity::Warning).child(
2741                    h_flex().w_full().children(
2742                        provider.render_accept_terms(
2743                            LanguageModelProviderTosView::ThreadEmptyState,
2744                            cx,
2745                        ),
2746                    ),
2747                )
2748            }
2749        }
2750    }
2751
2752    fn render_tool_use_limit_reached(
2753        &self,
2754        window: &mut Window,
2755        cx: &mut Context<Self>,
2756    ) -> Option<AnyElement> {
2757        let active_thread = match &self.active_view {
2758            ActiveView::Thread { thread, .. } => thread,
2759            ActiveView::ExternalAgentThread { .. } => {
2760                return None;
2761            }
2762            ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {
2763                return None;
2764            }
2765        };
2766
2767        let thread = active_thread.read(cx).thread().read(cx);
2768
2769        let tool_use_limit_reached = thread.tool_use_limit_reached();
2770        if !tool_use_limit_reached {
2771            return None;
2772        }
2773
2774        let model = thread.configured_model()?.model;
2775
2776        let focus_handle = self.focus_handle(cx);
2777
2778        let banner = Banner::new()
2779            .severity(ui::Severity::Info)
2780            .child(Label::new("Consecutive tool use limit reached.").size(LabelSize::Small))
2781            .action_slot(
2782                h_flex()
2783                    .gap_1()
2784                    .child(
2785                        Button::new("continue-conversation", "Continue")
2786                            .layer(ElevationIndex::ModalSurface)
2787                            .label_size(LabelSize::Small)
2788                            .key_binding(
2789                                KeyBinding::for_action_in(
2790                                    &ContinueThread,
2791                                    &focus_handle,
2792                                    window,
2793                                    cx,
2794                                )
2795                                .map(|kb| kb.size(rems_from_px(10.))),
2796                            )
2797                            .on_click(cx.listener(|this, _, window, cx| {
2798                                this.continue_conversation(window, cx);
2799                            })),
2800                    )
2801                    .when(model.supports_burn_mode(), |this| {
2802                        this.child(
2803                            Button::new("continue-burn-mode", "Continue with Burn Mode")
2804                                .style(ButtonStyle::Filled)
2805                                .style(ButtonStyle::Tinted(ui::TintColor::Accent))
2806                                .layer(ElevationIndex::ModalSurface)
2807                                .label_size(LabelSize::Small)
2808                                .key_binding(
2809                                    KeyBinding::for_action_in(
2810                                        &ContinueWithBurnMode,
2811                                        &focus_handle,
2812                                        window,
2813                                        cx,
2814                                    )
2815                                    .map(|kb| kb.size(rems_from_px(10.))),
2816                                )
2817                                .tooltip(Tooltip::text("Enable Burn Mode for unlimited tool use."))
2818                                .on_click({
2819                                    let active_thread = active_thread.clone();
2820                                    cx.listener(move |this, _, window, cx| {
2821                                        active_thread.update(cx, |active_thread, cx| {
2822                                            active_thread.thread().update(cx, |thread, _cx| {
2823                                                thread.set_completion_mode(CompletionMode::Burn);
2824                                            });
2825                                        });
2826                                        this.continue_conversation(window, cx);
2827                                    })
2828                                }),
2829                        )
2830                    }),
2831            );
2832
2833        Some(div().px_2().pb_2().child(banner).into_any_element())
2834    }
2835
2836    fn create_copy_button(&self, message: impl Into<String>) -> impl IntoElement {
2837        let message = message.into();
2838
2839        IconButton::new("copy", IconName::Copy)
2840            .icon_size(IconSize::Small)
2841            .icon_color(Color::Muted)
2842            .tooltip(Tooltip::text("Copy Error Message"))
2843            .on_click(move |_, _, cx| {
2844                cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
2845            })
2846    }
2847
2848    fn dismiss_error_button(
2849        &self,
2850        thread: &Entity<ActiveThread>,
2851        cx: &mut Context<Self>,
2852    ) -> impl IntoElement {
2853        IconButton::new("dismiss", IconName::Close)
2854            .icon_size(IconSize::Small)
2855            .icon_color(Color::Muted)
2856            .tooltip(Tooltip::text("Dismiss Error"))
2857            .on_click(cx.listener({
2858                let thread = thread.clone();
2859                move |_, _, _, cx| {
2860                    thread.update(cx, |this, _cx| {
2861                        this.clear_last_error();
2862                    });
2863
2864                    cx.notify();
2865                }
2866            }))
2867    }
2868
2869    fn upgrade_button(
2870        &self,
2871        thread: &Entity<ActiveThread>,
2872        cx: &mut Context<Self>,
2873    ) -> impl IntoElement {
2874        Button::new("upgrade", "Upgrade")
2875            .label_size(LabelSize::Small)
2876            .style(ButtonStyle::Tinted(ui::TintColor::Accent))
2877            .on_click(cx.listener({
2878                let thread = thread.clone();
2879                move |_, _, _, cx| {
2880                    thread.update(cx, |this, _cx| {
2881                        this.clear_last_error();
2882                    });
2883
2884                    cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx));
2885                    cx.notify();
2886                }
2887            }))
2888    }
2889
2890    fn error_callout_bg(&self, cx: &Context<Self>) -> Hsla {
2891        cx.theme().status().error.opacity(0.08)
2892    }
2893
2894    fn render_payment_required_error(
2895        &self,
2896        thread: &Entity<ActiveThread>,
2897        cx: &mut Context<Self>,
2898    ) -> AnyElement {
2899        const ERROR_MESSAGE: &str =
2900            "You reached your free usage limit. Upgrade to Zed Pro for more prompts.";
2901
2902        let icon = Icon::new(IconName::XCircle)
2903            .size(IconSize::Small)
2904            .color(Color::Error);
2905
2906        div()
2907            .border_t_1()
2908            .border_color(cx.theme().colors().border)
2909            .child(
2910                Callout::new()
2911                    .icon(icon)
2912                    .title("Free Usage Exceeded")
2913                    .description(ERROR_MESSAGE)
2914                    .tertiary_action(self.upgrade_button(thread, cx))
2915                    .secondary_action(self.create_copy_button(ERROR_MESSAGE))
2916                    .primary_action(self.dismiss_error_button(thread, cx))
2917                    .bg_color(self.error_callout_bg(cx)),
2918            )
2919            .into_any_element()
2920    }
2921
2922    fn render_model_request_limit_reached_error(
2923        &self,
2924        plan: Plan,
2925        thread: &Entity<ActiveThread>,
2926        cx: &mut Context<Self>,
2927    ) -> AnyElement {
2928        let error_message = match plan {
2929            Plan::ZedPro => "Upgrade to usage-based billing for more prompts.",
2930            Plan::ZedProTrial | Plan::ZedFree => "Upgrade to Zed Pro for more prompts.",
2931        };
2932
2933        let icon = Icon::new(IconName::XCircle)
2934            .size(IconSize::Small)
2935            .color(Color::Error);
2936
2937        div()
2938            .border_t_1()
2939            .border_color(cx.theme().colors().border)
2940            .child(
2941                Callout::new()
2942                    .icon(icon)
2943                    .title("Model Prompt Limit Reached")
2944                    .description(error_message)
2945                    .tertiary_action(self.upgrade_button(thread, cx))
2946                    .secondary_action(self.create_copy_button(error_message))
2947                    .primary_action(self.dismiss_error_button(thread, cx))
2948                    .bg_color(self.error_callout_bg(cx)),
2949            )
2950            .into_any_element()
2951    }
2952
2953    fn render_error_message(
2954        &self,
2955        header: SharedString,
2956        message: SharedString,
2957        thread: &Entity<ActiveThread>,
2958        cx: &mut Context<Self>,
2959    ) -> AnyElement {
2960        let message_with_header = format!("{}\n{}", header, message);
2961
2962        let icon = Icon::new(IconName::XCircle)
2963            .size(IconSize::Small)
2964            .color(Color::Error);
2965
2966        let retry_button = Button::new("retry", "Retry")
2967            .icon(IconName::RotateCw)
2968            .icon_position(IconPosition::Start)
2969            .icon_size(IconSize::Small)
2970            .label_size(LabelSize::Small)
2971            .on_click({
2972                let thread = thread.clone();
2973                move |_, window, cx| {
2974                    thread.update(cx, |thread, cx| {
2975                        thread.clear_last_error();
2976                        thread.thread().update(cx, |thread, cx| {
2977                            thread.retry_last_completion(Some(window.window_handle()), cx);
2978                        });
2979                    });
2980                }
2981            });
2982
2983        div()
2984            .border_t_1()
2985            .border_color(cx.theme().colors().border)
2986            .child(
2987                Callout::new()
2988                    .icon(icon)
2989                    .title(header)
2990                    .description(message.clone())
2991                    .primary_action(retry_button)
2992                    .secondary_action(self.dismiss_error_button(thread, cx))
2993                    .tertiary_action(self.create_copy_button(message_with_header))
2994                    .bg_color(self.error_callout_bg(cx)),
2995            )
2996            .into_any_element()
2997    }
2998
2999    fn render_retryable_error(
3000        &self,
3001        message: SharedString,
3002        can_enable_burn_mode: bool,
3003        thread: &Entity<ActiveThread>,
3004        cx: &mut Context<Self>,
3005    ) -> AnyElement {
3006        let icon = Icon::new(IconName::XCircle)
3007            .size(IconSize::Small)
3008            .color(Color::Error);
3009
3010        let retry_button = Button::new("retry", "Retry")
3011            .icon(IconName::RotateCw)
3012            .icon_position(IconPosition::Start)
3013            .icon_size(IconSize::Small)
3014            .label_size(LabelSize::Small)
3015            .on_click({
3016                let thread = thread.clone();
3017                move |_, window, cx| {
3018                    thread.update(cx, |thread, cx| {
3019                        thread.clear_last_error();
3020                        thread.thread().update(cx, |thread, cx| {
3021                            thread.retry_last_completion(Some(window.window_handle()), cx);
3022                        });
3023                    });
3024                }
3025            });
3026
3027        let mut callout = Callout::new()
3028            .icon(icon)
3029            .title("Error")
3030            .description(message.clone())
3031            .bg_color(self.error_callout_bg(cx))
3032            .primary_action(retry_button);
3033
3034        if can_enable_burn_mode {
3035            let burn_mode_button = Button::new("enable_burn_retry", "Enable Burn Mode and Retry")
3036                .icon(IconName::ZedBurnMode)
3037                .icon_position(IconPosition::Start)
3038                .icon_size(IconSize::Small)
3039                .label_size(LabelSize::Small)
3040                .on_click({
3041                    let thread = thread.clone();
3042                    move |_, window, cx| {
3043                        thread.update(cx, |thread, cx| {
3044                            thread.clear_last_error();
3045                            thread.thread().update(cx, |thread, cx| {
3046                                thread.enable_burn_mode_and_retry(Some(window.window_handle()), cx);
3047                            });
3048                        });
3049                    }
3050                });
3051            callout = callout.secondary_action(burn_mode_button);
3052        }
3053
3054        div()
3055            .border_t_1()
3056            .border_color(cx.theme().colors().border)
3057            .child(callout)
3058            .into_any_element()
3059    }
3060
3061    fn render_prompt_editor(
3062        &self,
3063        context_editor: &Entity<TextThreadEditor>,
3064        buffer_search_bar: &Entity<BufferSearchBar>,
3065        window: &mut Window,
3066        cx: &mut Context<Self>,
3067    ) -> Div {
3068        let mut registrar = buffer_search::DivRegistrar::new(
3069            |this, _, _cx| match &this.active_view {
3070                ActiveView::TextThread {
3071                    buffer_search_bar, ..
3072                } => Some(buffer_search_bar.clone()),
3073                _ => None,
3074            },
3075            cx,
3076        );
3077        BufferSearchBar::register(&mut registrar);
3078        registrar
3079            .into_div()
3080            .size_full()
3081            .relative()
3082            .map(|parent| {
3083                buffer_search_bar.update(cx, |buffer_search_bar, cx| {
3084                    if buffer_search_bar.is_dismissed() {
3085                        return parent;
3086                    }
3087                    parent.child(
3088                        div()
3089                            .p(DynamicSpacing::Base08.rems(cx))
3090                            .border_b_1()
3091                            .border_color(cx.theme().colors().border_variant)
3092                            .bg(cx.theme().colors().editor_background)
3093                            .child(buffer_search_bar.render(window, cx)),
3094                    )
3095                })
3096            })
3097            .child(context_editor.clone())
3098            .child(self.render_drag_target(cx))
3099    }
3100
3101    fn render_drag_target(&self, cx: &Context<Self>) -> Div {
3102        let is_local = self.project.read(cx).is_local();
3103        div()
3104            .invisible()
3105            .absolute()
3106            .top_0()
3107            .right_0()
3108            .bottom_0()
3109            .left_0()
3110            .bg(cx.theme().colors().drop_target_background)
3111            .drag_over::<DraggedTab>(|this, _, _, _| this.visible())
3112            .drag_over::<DraggedSelection>(|this, _, _, _| this.visible())
3113            .when(is_local, |this| {
3114                this.drag_over::<ExternalPaths>(|this, _, _, _| this.visible())
3115            })
3116            .on_drop(cx.listener(move |this, tab: &DraggedTab, window, cx| {
3117                let item = tab.pane.read(cx).item_for_index(tab.ix);
3118                let project_paths = item
3119                    .and_then(|item| item.project_path(cx))
3120                    .into_iter()
3121                    .collect::<Vec<_>>();
3122                this.handle_drop(project_paths, vec![], window, cx);
3123            }))
3124            .on_drop(
3125                cx.listener(move |this, selection: &DraggedSelection, window, cx| {
3126                    let project_paths = selection
3127                        .items()
3128                        .filter_map(|item| this.project.read(cx).path_for_entry(item.entry_id, cx))
3129                        .collect::<Vec<_>>();
3130                    this.handle_drop(project_paths, vec![], window, cx);
3131                }),
3132            )
3133            .on_drop(cx.listener(move |this, paths: &ExternalPaths, window, cx| {
3134                let tasks = paths
3135                    .paths()
3136                    .into_iter()
3137                    .map(|path| {
3138                        Workspace::project_path_for_path(this.project.clone(), &path, false, cx)
3139                    })
3140                    .collect::<Vec<_>>();
3141                cx.spawn_in(window, async move |this, cx| {
3142                    let mut paths = vec![];
3143                    let mut added_worktrees = vec![];
3144                    let opened_paths = futures::future::join_all(tasks).await;
3145                    for entry in opened_paths {
3146                        if let Some((worktree, project_path)) = entry.log_err() {
3147                            added_worktrees.push(worktree);
3148                            paths.push(project_path);
3149                        }
3150                    }
3151                    this.update_in(cx, |this, window, cx| {
3152                        this.handle_drop(paths, added_worktrees, window, cx);
3153                    })
3154                    .ok();
3155                })
3156                .detach();
3157            }))
3158    }
3159
3160    fn handle_drop(
3161        &mut self,
3162        paths: Vec<ProjectPath>,
3163        added_worktrees: Vec<Entity<Worktree>>,
3164        window: &mut Window,
3165        cx: &mut Context<Self>,
3166    ) {
3167        match &self.active_view {
3168            ActiveView::Thread { thread, .. } => {
3169                let context_store = thread.read(cx).context_store().clone();
3170                context_store.update(cx, move |context_store, cx| {
3171                    let mut tasks = Vec::new();
3172                    for project_path in &paths {
3173                        tasks.push(context_store.add_file_from_path(
3174                            project_path.clone(),
3175                            false,
3176                            cx,
3177                        ));
3178                    }
3179                    cx.background_spawn(async move {
3180                        futures::future::join_all(tasks).await;
3181                        // Need to hold onto the worktrees until they have already been used when
3182                        // opening the buffers.
3183                        drop(added_worktrees);
3184                    })
3185                    .detach();
3186                });
3187            }
3188            ActiveView::ExternalAgentThread { thread_view } => {
3189                thread_view.update(cx, |thread_view, cx| {
3190                    thread_view.insert_dragged_files(paths, added_worktrees, window, cx);
3191                });
3192            }
3193            ActiveView::TextThread { context_editor, .. } => {
3194                context_editor.update(cx, |context_editor, cx| {
3195                    TextThreadEditor::insert_dragged_files(
3196                        context_editor,
3197                        paths,
3198                        added_worktrees,
3199                        window,
3200                        cx,
3201                    );
3202                });
3203            }
3204            ActiveView::History | ActiveView::Configuration => {}
3205        }
3206    }
3207
3208    fn key_context(&self) -> KeyContext {
3209        let mut key_context = KeyContext::new_with_defaults();
3210        key_context.add("AgentPanel");
3211        match &self.active_view {
3212            ActiveView::ExternalAgentThread { .. } => key_context.add("external_agent_thread"),
3213            ActiveView::TextThread { .. } => key_context.add("prompt_editor"),
3214            ActiveView::Thread { .. } | ActiveView::History | ActiveView::Configuration => {}
3215        }
3216        key_context
3217    }
3218}
3219
3220impl Render for AgentPanel {
3221    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3222        // WARNING: Changes to this element hierarchy can have
3223        // non-obvious implications to the layout of children.
3224        //
3225        // If you need to change it, please confirm:
3226        // - The message editor expands (cmd-option-esc) correctly
3227        // - When expanded, the buttons at the bottom of the panel are displayed correctly
3228        // - Font size works as expected and can be changed with cmd-+/cmd-
3229        // - Scrolling in all views works as expected
3230        // - Files can be dropped into the panel
3231        let content = v_flex()
3232            .relative()
3233            .size_full()
3234            .justify_between()
3235            .key_context(self.key_context())
3236            .on_action(cx.listener(Self::cancel))
3237            .on_action(cx.listener(|this, action: &NewThread, window, cx| {
3238                this.new_thread(action, window, cx);
3239            }))
3240            .on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
3241                this.open_history(window, cx);
3242            }))
3243            .on_action(cx.listener(|this, _: &OpenSettings, window, cx| {
3244                this.open_configuration(window, cx);
3245            }))
3246            .on_action(cx.listener(Self::open_active_thread_as_markdown))
3247            .on_action(cx.listener(Self::deploy_rules_library))
3248            .on_action(cx.listener(Self::open_agent_diff))
3249            .on_action(cx.listener(Self::go_back))
3250            .on_action(cx.listener(Self::toggle_navigation_menu))
3251            .on_action(cx.listener(Self::toggle_options_menu))
3252            .on_action(cx.listener(Self::increase_font_size))
3253            .on_action(cx.listener(Self::decrease_font_size))
3254            .on_action(cx.listener(Self::reset_font_size))
3255            .on_action(cx.listener(Self::toggle_zoom))
3256            .on_action(cx.listener(|this, _: &ContinueThread, window, cx| {
3257                this.continue_conversation(window, cx);
3258            }))
3259            .on_action(cx.listener(|this, _: &ContinueWithBurnMode, window, cx| {
3260                match &this.active_view {
3261                    ActiveView::Thread { thread, .. } => {
3262                        thread.update(cx, |active_thread, cx| {
3263                            active_thread.thread().update(cx, |thread, _cx| {
3264                                thread.set_completion_mode(CompletionMode::Burn);
3265                            });
3266                        });
3267                        this.continue_conversation(window, cx);
3268                    }
3269                    ActiveView::ExternalAgentThread { .. } => {}
3270                    ActiveView::TextThread { .. }
3271                    | ActiveView::History
3272                    | ActiveView::Configuration => {}
3273                }
3274            }))
3275            .on_action(cx.listener(Self::toggle_burn_mode))
3276            .child(self.render_toolbar(window, cx))
3277            .children(self.render_onboarding(window, cx))
3278            .map(|parent| match &self.active_view {
3279                ActiveView::Thread {
3280                    thread,
3281                    message_editor,
3282                    ..
3283                } => parent
3284                    .child(
3285                        if thread.read(cx).is_empty() && !self.should_render_onboarding(cx) {
3286                            self.render_thread_empty_state(window, cx)
3287                                .into_any_element()
3288                        } else {
3289                            thread.clone().into_any_element()
3290                        },
3291                    )
3292                    .children(self.render_tool_use_limit_reached(window, cx))
3293                    .when_some(thread.read(cx).last_error(), |this, last_error| {
3294                        this.child(
3295                            div()
3296                                .child(match last_error {
3297                                    ThreadError::PaymentRequired => {
3298                                        self.render_payment_required_error(thread, cx)
3299                                    }
3300                                    ThreadError::ModelRequestLimitReached { plan } => self
3301                                        .render_model_request_limit_reached_error(plan, thread, cx),
3302                                    ThreadError::Message { header, message } => {
3303                                        self.render_error_message(header, message, thread, cx)
3304                                    }
3305                                    ThreadError::RetryableError {
3306                                        message,
3307                                        can_enable_burn_mode,
3308                                    } => self.render_retryable_error(
3309                                        message,
3310                                        can_enable_burn_mode,
3311                                        thread,
3312                                        cx,
3313                                    ),
3314                                })
3315                                .into_any(),
3316                        )
3317                    })
3318                    .child(h_flex().relative().child(message_editor.clone()).when(
3319                        !LanguageModelRegistry::read_global(cx).has_authenticated_provider(cx),
3320                        |this| this.child(self.render_backdrop(cx)),
3321                    ))
3322                    .child(self.render_drag_target(cx)),
3323                ActiveView::ExternalAgentThread { thread_view, .. } => parent
3324                    .child(thread_view.clone())
3325                    .child(self.render_drag_target(cx)),
3326                ActiveView::History => parent.child(self.history.clone()),
3327                ActiveView::TextThread {
3328                    context_editor,
3329                    buffer_search_bar,
3330                    ..
3331                } => {
3332                    let model_registry = LanguageModelRegistry::read_global(cx);
3333                    let configuration_error =
3334                        model_registry.configuration_error(model_registry.default_model(), cx);
3335                    parent
3336                        .map(|this| {
3337                            if !self.should_render_onboarding(cx)
3338                                && let Some(err) = configuration_error.as_ref()
3339                            {
3340                                this.child(
3341                                    div().bg(cx.theme().colors().editor_background).p_2().child(
3342                                        self.render_configuration_error(
3343                                            err,
3344                                            &self.focus_handle(cx),
3345                                            window,
3346                                            cx,
3347                                        ),
3348                                    ),
3349                                )
3350                            } else {
3351                                this
3352                            }
3353                        })
3354                        .child(self.render_prompt_editor(
3355                            context_editor,
3356                            buffer_search_bar,
3357                            window,
3358                            cx,
3359                        ))
3360                }
3361                ActiveView::Configuration => parent.children(self.configuration.clone()),
3362            })
3363            .children(self.render_trial_end_upsell(window, cx));
3364
3365        match self.active_view.which_font_size_used() {
3366            WhichFontSize::AgentFont => {
3367                WithRemSize::new(ThemeSettings::get_global(cx).agent_font_size(cx))
3368                    .size_full()
3369                    .child(content)
3370                    .into_any()
3371            }
3372            _ => content.into_any(),
3373        }
3374    }
3375}
3376
3377struct PromptLibraryInlineAssist {
3378    workspace: WeakEntity<Workspace>,
3379}
3380
3381impl PromptLibraryInlineAssist {
3382    pub fn new(workspace: WeakEntity<Workspace>) -> Self {
3383        Self { workspace }
3384    }
3385}
3386
3387impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
3388    fn assist(
3389        &self,
3390        prompt_editor: &Entity<Editor>,
3391        initial_prompt: Option<String>,
3392        window: &mut Window,
3393        cx: &mut Context<RulesLibrary>,
3394    ) {
3395        InlineAssistant::update_global(cx, |assistant, cx| {
3396            let Some(project) = self
3397                .workspace
3398                .upgrade()
3399                .map(|workspace| workspace.read(cx).project().downgrade())
3400            else {
3401                return;
3402            };
3403            let prompt_store = None;
3404            let thread_store = None;
3405            let text_thread_store = None;
3406            let context_store = cx.new(|_| ContextStore::new(project.clone(), None));
3407            assistant.assist(
3408                &prompt_editor,
3409                self.workspace.clone(),
3410                context_store,
3411                project,
3412                prompt_store,
3413                thread_store,
3414                text_thread_store,
3415                initial_prompt,
3416                window,
3417                cx,
3418            )
3419        })
3420    }
3421
3422    fn focus_agent_panel(
3423        &self,
3424        workspace: &mut Workspace,
3425        window: &mut Window,
3426        cx: &mut Context<Workspace>,
3427    ) -> bool {
3428        workspace.focus_panel::<AgentPanel>(window, cx).is_some()
3429    }
3430}
3431
3432pub struct ConcreteAssistantPanelDelegate;
3433
3434impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
3435    fn active_context_editor(
3436        &self,
3437        workspace: &mut Workspace,
3438        _window: &mut Window,
3439        cx: &mut Context<Workspace>,
3440    ) -> Option<Entity<TextThreadEditor>> {
3441        let panel = workspace.panel::<AgentPanel>(cx)?;
3442        panel.read(cx).active_context_editor()
3443    }
3444
3445    fn open_saved_context(
3446        &self,
3447        workspace: &mut Workspace,
3448        path: Arc<Path>,
3449        window: &mut Window,
3450        cx: &mut Context<Workspace>,
3451    ) -> Task<Result<()>> {
3452        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
3453            return Task::ready(Err(anyhow!("Agent panel not found")));
3454        };
3455
3456        panel.update(cx, |panel, cx| {
3457            panel.open_saved_prompt_editor(path, window, cx)
3458        })
3459    }
3460
3461    fn open_remote_context(
3462        &self,
3463        _workspace: &mut Workspace,
3464        _context_id: assistant_context::ContextId,
3465        _window: &mut Window,
3466        _cx: &mut Context<Workspace>,
3467    ) -> Task<Result<Entity<TextThreadEditor>>> {
3468        Task::ready(Err(anyhow!("opening remote context not implemented")))
3469    }
3470
3471    fn quote_selection(
3472        &self,
3473        workspace: &mut Workspace,
3474        selection_ranges: Vec<Range<Anchor>>,
3475        buffer: Entity<MultiBuffer>,
3476        window: &mut Window,
3477        cx: &mut Context<Workspace>,
3478    ) {
3479        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
3480            return;
3481        };
3482
3483        if !panel.focus_handle(cx).contains_focused(window, cx) {
3484            workspace.toggle_panel_focus::<AgentPanel>(window, cx);
3485        }
3486
3487        panel.update(cx, |_, cx| {
3488            // Wait to create a new context until the workspace is no longer
3489            // being updated.
3490            cx.defer_in(window, move |panel, window, cx| {
3491                if let Some(message_editor) = panel.active_message_editor() {
3492                    message_editor.update(cx, |message_editor, cx| {
3493                        message_editor.context_store().update(cx, |store, cx| {
3494                            let buffer = buffer.read(cx);
3495                            let selection_ranges = selection_ranges
3496                                .into_iter()
3497                                .flat_map(|range| {
3498                                    let (start_buffer, start) =
3499                                        buffer.text_anchor_for_position(range.start, cx)?;
3500                                    let (end_buffer, end) =
3501                                        buffer.text_anchor_for_position(range.end, cx)?;
3502                                    if start_buffer != end_buffer {
3503                                        return None;
3504                                    }
3505                                    Some((start_buffer, start..end))
3506                                })
3507                                .collect::<Vec<_>>();
3508
3509                            for (buffer, range) in selection_ranges {
3510                                store.add_selection(buffer, range, cx);
3511                            }
3512                        })
3513                    })
3514                } else if let Some(context_editor) = panel.active_context_editor() {
3515                    let snapshot = buffer.read(cx).snapshot(cx);
3516                    let selection_ranges = selection_ranges
3517                        .into_iter()
3518                        .map(|range| range.to_point(&snapshot))
3519                        .collect::<Vec<_>>();
3520
3521                    context_editor.update(cx, |context_editor, cx| {
3522                        context_editor.quote_ranges(selection_ranges, snapshot, window, cx)
3523                    });
3524                }
3525            });
3526        });
3527    }
3528}
3529
3530struct OnboardingUpsell;
3531
3532impl Dismissable for OnboardingUpsell {
3533    const KEY: &'static str = "dismissed-trial-upsell";
3534}
3535
3536struct TrialEndUpsell;
3537
3538impl Dismissable for TrialEndUpsell {
3539    const KEY: &'static str = "dismissed-trial-end-upsell";
3540}