agent_panel.rs

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