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