agent_panel.rs

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