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, ElevationIndex, KeyBinding, PopoverMenu,
  69    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 => "Gemini CLI",
 249            Self::ClaudeCode => "Claude Code",
 250        }
 251    }
 252
 253    fn icon(self) -> Option<IconName> {
 254        match self {
 255            Self::Zed | Self::NativeAgent | Self::TextThread => None,
 256            Self::Gemini => Some(IconName::AiGemini),
 257            Self::ClaudeCode => Some(IconName::AiClaude),
 258        }
 259    }
 260}
 261
 262impl ActiveView {
 263    pub fn which_font_size_used(&self) -> WhichFontSize {
 264        match self {
 265            ActiveView::Thread { .. }
 266            | ActiveView::ExternalAgentThread { .. }
 267            | ActiveView::History => WhichFontSize::AgentFont,
 268            ActiveView::TextThread { .. } => WhichFontSize::BufferFont,
 269            ActiveView::Configuration => WhichFontSize::None,
 270        }
 271    }
 272
 273    pub fn thread(
 274        active_thread: Entity<ActiveThread>,
 275        message_editor: Entity<MessageEditor>,
 276        window: &mut Window,
 277        cx: &mut Context<AgentPanel>,
 278    ) -> Self {
 279        let summary = active_thread.read(cx).summary(cx).or_default();
 280
 281        let editor = cx.new(|cx| {
 282            let mut editor = Editor::single_line(window, cx);
 283            editor.set_text(summary.clone(), window, cx);
 284            editor
 285        });
 286
 287        let subscriptions = vec![
 288            cx.subscribe(&message_editor, |this, _, event, cx| match event {
 289                MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => {
 290                    cx.notify();
 291                }
 292                MessageEditorEvent::ScrollThreadToBottom => match &this.active_view {
 293                    ActiveView::Thread { thread, .. } => {
 294                        thread.update(cx, |thread, cx| {
 295                            thread.scroll_to_bottom(cx);
 296                        });
 297                    }
 298                    ActiveView::ExternalAgentThread { .. } => {}
 299                    ActiveView::TextThread { .. }
 300                    | ActiveView::History
 301                    | ActiveView::Configuration => {}
 302                },
 303            }),
 304            window.subscribe(&editor, cx, {
 305                {
 306                    let thread = active_thread.clone();
 307                    move |editor, event, window, cx| match event {
 308                        EditorEvent::BufferEdited => {
 309                            let new_summary = editor.read(cx).text(cx);
 310
 311                            thread.update(cx, |thread, cx| {
 312                                thread.thread().update(cx, |thread, cx| {
 313                                    thread.set_summary(new_summary, cx);
 314                                });
 315                            })
 316                        }
 317                        EditorEvent::Blurred => {
 318                            if editor.read(cx).text(cx).is_empty() {
 319                                let summary = thread.read(cx).summary(cx).or_default();
 320
 321                                editor.update(cx, |editor, cx| {
 322                                    editor.set_text(summary, window, cx);
 323                                });
 324                            }
 325                        }
 326                        _ => {}
 327                    }
 328                }
 329            }),
 330            cx.subscribe(&active_thread, |_, _, event, cx| match &event {
 331                ActiveThreadEvent::EditingMessageTokenCountChanged => {
 332                    cx.notify();
 333                }
 334            }),
 335            cx.subscribe_in(&active_thread.read(cx).thread().clone(), window, {
 336                let editor = editor.clone();
 337                move |_, thread, event, window, cx| match event {
 338                    ThreadEvent::SummaryGenerated => {
 339                        let summary = thread.read(cx).summary().or_default();
 340
 341                        editor.update(cx, |editor, cx| {
 342                            editor.set_text(summary, window, cx);
 343                        })
 344                    }
 345                    ThreadEvent::MessageAdded(_) => {
 346                        cx.notify();
 347                    }
 348                    _ => {}
 349                }
 350            }),
 351        ];
 352
 353        Self::Thread {
 354            change_title_editor: editor,
 355            thread: active_thread,
 356            message_editor,
 357            _subscriptions: subscriptions,
 358        }
 359    }
 360
 361    pub fn prompt_editor(
 362        context_editor: Entity<TextThreadEditor>,
 363        history_store: Entity<HistoryStore>,
 364        acp_history_store: Entity<agent2::HistoryStore>,
 365        language_registry: Arc<LanguageRegistry>,
 366        window: &mut Window,
 367        cx: &mut App,
 368    ) -> Self {
 369        let title = context_editor.read(cx).title(cx).to_string();
 370
 371        let editor = cx.new(|cx| {
 372            let mut editor = Editor::single_line(window, cx);
 373            editor.set_text(title, window, cx);
 374            editor
 375        });
 376
 377        // This is a workaround for `editor.set_text` emitting a `BufferEdited` event, which would
 378        // cause a custom summary to be set. The presence of this custom summary would cause
 379        // summarization to not happen.
 380        let mut suppress_first_edit = true;
 381
 382        let subscriptions = vec![
 383            window.subscribe(&editor, cx, {
 384                {
 385                    let context_editor = context_editor.clone();
 386                    move |editor, event, window, cx| match event {
 387                        EditorEvent::BufferEdited => {
 388                            if suppress_first_edit {
 389                                suppress_first_edit = false;
 390                                return;
 391                            }
 392                            let new_summary = editor.read(cx).text(cx);
 393
 394                            context_editor.update(cx, |context_editor, cx| {
 395                                context_editor
 396                                    .context()
 397                                    .update(cx, |assistant_context, cx| {
 398                                        assistant_context.set_custom_summary(new_summary, cx);
 399                                    })
 400                            })
 401                        }
 402                        EditorEvent::Blurred => {
 403                            if editor.read(cx).text(cx).is_empty() {
 404                                let summary = context_editor
 405                                    .read(cx)
 406                                    .context()
 407                                    .read(cx)
 408                                    .summary()
 409                                    .or_default();
 410
 411                                editor.update(cx, |editor, cx| {
 412                                    editor.set_text(summary, window, cx);
 413                                });
 414                            }
 415                        }
 416                        _ => {}
 417                    }
 418                }
 419            }),
 420            window.subscribe(&context_editor.read(cx).context().clone(), cx, {
 421                let editor = editor.clone();
 422                move |assistant_context, event, window, cx| match event {
 423                    ContextEvent::SummaryGenerated => {
 424                        let summary = assistant_context.read(cx).summary().or_default();
 425
 426                        editor.update(cx, |editor, cx| {
 427                            editor.set_text(summary, window, cx);
 428                        })
 429                    }
 430                    ContextEvent::PathChanged { old_path, new_path } => {
 431                        history_store.update(cx, |history_store, cx| {
 432                            if let Some(old_path) = old_path {
 433                                history_store
 434                                    .replace_recently_opened_text_thread(old_path, new_path, cx);
 435                            } else {
 436                                history_store.push_recently_opened_entry(
 437                                    HistoryEntryId::Context(new_path.clone()),
 438                                    cx,
 439                                );
 440                            }
 441                        });
 442
 443                        acp_history_store.update(cx, |history_store, cx| {
 444                            if let Some(old_path) = old_path {
 445                                history_store
 446                                    .replace_recently_opened_text_thread(old_path, new_path, cx);
 447                            } else {
 448                                history_store.push_recently_opened_entry(
 449                                    agent2::HistoryEntryId::TextThread(new_path.clone()),
 450                                    cx,
 451                                );
 452                            }
 453                        });
 454                    }
 455                    _ => {}
 456                }
 457            }),
 458        ];
 459
 460        let buffer_search_bar =
 461            cx.new(|cx| BufferSearchBar::new(Some(language_registry), window, cx));
 462        buffer_search_bar.update(cx, |buffer_search_bar, cx| {
 463            buffer_search_bar.set_active_pane_item(Some(&context_editor), window, cx)
 464        });
 465
 466        Self::TextThread {
 467            context_editor,
 468            title_editor: editor,
 469            buffer_search_bar,
 470            _subscriptions: subscriptions,
 471        }
 472    }
 473}
 474
 475pub struct AgentPanel {
 476    workspace: WeakEntity<Workspace>,
 477    user_store: Entity<UserStore>,
 478    project: Entity<Project>,
 479    fs: Arc<dyn Fs>,
 480    language_registry: Arc<LanguageRegistry>,
 481    thread_store: Entity<ThreadStore>,
 482    acp_history: Entity<AcpThreadHistory>,
 483    acp_history_store: Entity<agent2::HistoryStore>,
 484    _default_model_subscription: Subscription,
 485    context_store: Entity<TextThreadStore>,
 486    prompt_store: Option<Entity<PromptStore>>,
 487    inline_assist_context_store: Entity<ContextStore>,
 488    configuration: Option<Entity<AgentConfiguration>>,
 489    configuration_subscription: Option<Subscription>,
 490    local_timezone: UtcOffset,
 491    active_view: ActiveView,
 492    previous_view: Option<ActiveView>,
 493    history_store: Entity<HistoryStore>,
 494    history: Entity<ThreadHistory>,
 495    hovered_recent_history_item: Option<usize>,
 496    new_thread_menu_handle: PopoverMenuHandle<ContextMenu>,
 497    agent_panel_menu_handle: PopoverMenuHandle<ContextMenu>,
 498    assistant_navigation_menu_handle: PopoverMenuHandle<ContextMenu>,
 499    assistant_navigation_menu: Option<Entity<ContextMenu>>,
 500    width: Option<Pixels>,
 501    height: Option<Pixels>,
 502    zoomed: bool,
 503    pending_serialization: Option<Task<Result<()>>>,
 504    onboarding: Entity<AgentPanelOnboarding>,
 505    selected_agent: AgentType,
 506}
 507
 508impl AgentPanel {
 509    fn serialize(&mut self, cx: &mut Context<Self>) {
 510        let width = self.width;
 511        let selected_agent = self.selected_agent;
 512        self.pending_serialization = Some(cx.background_spawn(async move {
 513            KEY_VALUE_STORE
 514                .write_kvp(
 515                    AGENT_PANEL_KEY.into(),
 516                    serde_json::to_string(&SerializedAgentPanel {
 517                        width,
 518                        selected_agent: Some(selected_agent),
 519                    })?,
 520                )
 521                .await?;
 522            anyhow::Ok(())
 523        }));
 524    }
 525    pub fn load(
 526        workspace: WeakEntity<Workspace>,
 527        prompt_builder: Arc<PromptBuilder>,
 528        mut cx: AsyncWindowContext,
 529    ) -> Task<Result<Entity<Self>>> {
 530        let prompt_store = cx.update(|_window, cx| PromptStore::global(cx));
 531        cx.spawn(async move |cx| {
 532            let prompt_store = match prompt_store {
 533                Ok(prompt_store) => prompt_store.await.ok(),
 534                Err(_) => None,
 535            };
 536            let tools = cx.new(|_| ToolWorkingSet::default())?;
 537            let thread_store = workspace
 538                .update(cx, |workspace, cx| {
 539                    let project = workspace.project().clone();
 540                    ThreadStore::load(
 541                        project,
 542                        tools.clone(),
 543                        prompt_store.clone(),
 544                        prompt_builder.clone(),
 545                        cx,
 546                    )
 547                })?
 548                .await?;
 549
 550            let slash_commands = Arc::new(SlashCommandWorkingSet::default());
 551            let context_store = workspace
 552                .update(cx, |workspace, cx| {
 553                    let project = workspace.project().clone();
 554                    assistant_context::ContextStore::new(
 555                        project,
 556                        prompt_builder.clone(),
 557                        slash_commands,
 558                        cx,
 559                    )
 560                })?
 561                .await?;
 562
 563            let serialized_panel = if let Some(panel) = cx
 564                .background_spawn(async move { KEY_VALUE_STORE.read_kvp(AGENT_PANEL_KEY) })
 565                .await
 566                .log_err()
 567                .flatten()
 568            {
 569                Some(serde_json::from_str::<SerializedAgentPanel>(&panel)?)
 570            } else {
 571                None
 572            };
 573
 574            let panel = workspace.update_in(cx, |workspace, window, cx| {
 575                let panel = cx.new(|cx| {
 576                    Self::new(
 577                        workspace,
 578                        thread_store,
 579                        context_store,
 580                        prompt_store,
 581                        window,
 582                        cx,
 583                    )
 584                });
 585                if let Some(serialized_panel) = serialized_panel {
 586                    panel.update(cx, |panel, cx| {
 587                        panel.width = serialized_panel.width.map(|w| w.round());
 588                        if let Some(selected_agent) = serialized_panel.selected_agent {
 589                            panel.selected_agent = selected_agent;
 590                            panel.new_agent_thread(selected_agent, window, cx);
 591                        }
 592                        cx.notify();
 593                    });
 594                }
 595                panel
 596            })?;
 597
 598            Ok(panel)
 599        })
 600    }
 601
 602    fn new(
 603        workspace: &Workspace,
 604        thread_store: Entity<ThreadStore>,
 605        context_store: Entity<TextThreadStore>,
 606        prompt_store: Option<Entity<PromptStore>>,
 607        window: &mut Window,
 608        cx: &mut Context<Self>,
 609    ) -> Self {
 610        let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
 611        let fs = workspace.app_state().fs.clone();
 612        let user_store = workspace.app_state().user_store.clone();
 613        let project = workspace.project();
 614        let language_registry = project.read(cx).languages().clone();
 615        let client = workspace.client().clone();
 616        let workspace = workspace.weak_handle();
 617        let weak_self = cx.entity().downgrade();
 618
 619        let message_editor_context_store =
 620            cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
 621        let inline_assist_context_store =
 622            cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
 623
 624        let thread_id = thread.read(cx).id().clone();
 625
 626        let history_store = cx.new(|cx| {
 627            HistoryStore::new(
 628                thread_store.clone(),
 629                context_store.clone(),
 630                [HistoryEntryId::Thread(thread_id)],
 631                cx,
 632            )
 633        });
 634
 635        let message_editor = cx.new(|cx| {
 636            MessageEditor::new(
 637                fs.clone(),
 638                workspace.clone(),
 639                message_editor_context_store.clone(),
 640                prompt_store.clone(),
 641                thread_store.downgrade(),
 642                context_store.downgrade(),
 643                Some(history_store.downgrade()),
 644                thread.clone(),
 645                window,
 646                cx,
 647            )
 648        });
 649
 650        let acp_history_store = cx.new(|cx| agent2::HistoryStore::new(context_store.clone(), cx));
 651        let acp_history = cx.new(|cx| AcpThreadHistory::new(acp_history_store.clone(), window, cx));
 652        cx.subscribe_in(
 653            &acp_history,
 654            window,
 655            |this, _, event, window, cx| match event {
 656                ThreadHistoryEvent::Open(HistoryEntry::AcpThread(thread)) => {
 657                    this.external_thread(
 658                        Some(crate::ExternalAgent::NativeAgent),
 659                        Some(thread.clone()),
 660                        window,
 661                        cx,
 662                    );
 663                }
 664                ThreadHistoryEvent::Open(HistoryEntry::TextThread(thread)) => {
 665                    this.open_saved_prompt_editor(thread.path.clone(), window, cx)
 666                        .detach_and_log_err(cx);
 667                }
 668            },
 669        )
 670        .detach();
 671
 672        cx.observe(&history_store, |_, _, cx| cx.notify()).detach();
 673
 674        let active_thread = cx.new(|cx| {
 675            ActiveThread::new(
 676                thread.clone(),
 677                thread_store.clone(),
 678                context_store.clone(),
 679                message_editor_context_store.clone(),
 680                language_registry.clone(),
 681                workspace.clone(),
 682                window,
 683                cx,
 684            )
 685        });
 686
 687        let panel_type = AgentSettings::get_global(cx).default_view;
 688        let active_view = match panel_type {
 689            DefaultView::Thread => ActiveView::thread(active_thread, message_editor, window, cx),
 690            DefaultView::TextThread => {
 691                let context =
 692                    context_store.update(cx, |context_store, cx| context_store.create(cx));
 693                let lsp_adapter_delegate = make_lsp_adapter_delegate(&project.clone(), cx).unwrap();
 694                let context_editor = cx.new(|cx| {
 695                    let mut editor = TextThreadEditor::for_context(
 696                        context,
 697                        fs.clone(),
 698                        workspace.clone(),
 699                        project.clone(),
 700                        lsp_adapter_delegate,
 701                        window,
 702                        cx,
 703                    );
 704                    editor.insert_default_prompt(window, cx);
 705                    editor
 706                });
 707                ActiveView::prompt_editor(
 708                    context_editor,
 709                    history_store.clone(),
 710                    acp_history_store.clone(),
 711                    language_registry.clone(),
 712                    window,
 713                    cx,
 714                )
 715            }
 716        };
 717
 718        AgentDiff::set_active_thread(&workspace, thread.clone(), window, cx);
 719
 720        let weak_panel = weak_self.clone();
 721
 722        window.defer(cx, move |window, cx| {
 723            let panel = weak_panel.clone();
 724            let assistant_navigation_menu =
 725                ContextMenu::build_persistent(window, cx, move |mut menu, _window, cx| {
 726                    if let Some(panel) = panel.upgrade() {
 727                        if cx.has_flag::<GeminiAndNativeFeatureFlag>() {
 728                            menu = Self::populate_recently_opened_menu_section_new(menu, panel, cx);
 729                        } else {
 730                            menu = Self::populate_recently_opened_menu_section_old(menu, panel, cx);
 731                        }
 732                    }
 733                    menu.action("View All", Box::new(OpenHistory))
 734                        .end_slot_action(DeleteRecentlyOpenThread.boxed_clone())
 735                        .fixed_width(px(320.).into())
 736                        .keep_open_on_confirm(false)
 737                        .key_context("NavigationMenu")
 738                });
 739            weak_panel
 740                .update(cx, |panel, cx| {
 741                    cx.subscribe_in(
 742                        &assistant_navigation_menu,
 743                        window,
 744                        |_, menu, _: &DismissEvent, window, cx| {
 745                            menu.update(cx, |menu, _| {
 746                                menu.clear_selected();
 747                            });
 748                            cx.focus_self(window);
 749                        },
 750                    )
 751                    .detach();
 752                    panel.assistant_navigation_menu = Some(assistant_navigation_menu);
 753                })
 754                .ok();
 755        });
 756
 757        let _default_model_subscription =
 758            cx.subscribe(
 759                &LanguageModelRegistry::global(cx),
 760                |this, _, event: &language_model::Event, cx| {
 761                    if let language_model::Event::DefaultModelChanged = event {
 762                        match &this.active_view {
 763                            ActiveView::Thread { thread, .. } => {
 764                                thread.read(cx).thread().clone().update(cx, |thread, cx| {
 765                                    thread.get_or_init_configured_model(cx)
 766                                });
 767                            }
 768                            ActiveView::ExternalAgentThread { .. }
 769                            | ActiveView::TextThread { .. }
 770                            | ActiveView::History
 771                            | ActiveView::Configuration => {}
 772                        }
 773                    }
 774                },
 775            );
 776
 777        let onboarding = cx.new(|cx| {
 778            AgentPanelOnboarding::new(
 779                user_store.clone(),
 780                client,
 781                |_window, cx| {
 782                    OnboardingUpsell::set_dismissed(true, cx);
 783                },
 784                cx,
 785            )
 786        });
 787
 788        Self {
 789            active_view,
 790            workspace,
 791            user_store,
 792            project: project.clone(),
 793            fs: fs.clone(),
 794            language_registry,
 795            thread_store: thread_store.clone(),
 796            _default_model_subscription,
 797            context_store,
 798            prompt_store,
 799            configuration: None,
 800            configuration_subscription: None,
 801            local_timezone: UtcOffset::from_whole_seconds(
 802                chrono::Local::now().offset().local_minus_utc(),
 803            )
 804            .unwrap(),
 805            inline_assist_context_store,
 806            previous_view: None,
 807            history_store: history_store.clone(),
 808            history: cx.new(|cx| ThreadHistory::new(weak_self, history_store, window, cx)),
 809            hovered_recent_history_item: None,
 810            new_thread_menu_handle: PopoverMenuHandle::default(),
 811            agent_panel_menu_handle: PopoverMenuHandle::default(),
 812            assistant_navigation_menu_handle: PopoverMenuHandle::default(),
 813            assistant_navigation_menu: None,
 814            width: None,
 815            height: None,
 816            zoomed: false,
 817            pending_serialization: None,
 818            onboarding,
 819            acp_history,
 820            acp_history_store,
 821            selected_agent: AgentType::default(),
 822        }
 823    }
 824
 825    pub fn toggle_focus(
 826        workspace: &mut Workspace,
 827        _: &ToggleFocus,
 828        window: &mut Window,
 829        cx: &mut Context<Workspace>,
 830    ) {
 831        if workspace
 832            .panel::<Self>(cx)
 833            .is_some_and(|panel| panel.read(cx).enabled(cx))
 834            && !DisableAiSettings::get_global(cx).disable_ai
 835        {
 836            workspace.toggle_panel_focus::<Self>(window, cx);
 837        }
 838    }
 839
 840    pub(crate) fn local_timezone(&self) -> UtcOffset {
 841        self.local_timezone
 842    }
 843
 844    pub(crate) fn prompt_store(&self) -> &Option<Entity<PromptStore>> {
 845        &self.prompt_store
 846    }
 847
 848    pub(crate) fn inline_assist_context_store(&self) -> &Entity<ContextStore> {
 849        &self.inline_assist_context_store
 850    }
 851
 852    pub(crate) fn thread_store(&self) -> &Entity<ThreadStore> {
 853        &self.thread_store
 854    }
 855
 856    pub(crate) fn text_thread_store(&self) -> &Entity<TextThreadStore> {
 857        &self.context_store
 858    }
 859
 860    fn cancel(&mut self, _: &editor::actions::Cancel, window: &mut Window, cx: &mut Context<Self>) {
 861        match &self.active_view {
 862            ActiveView::Thread { thread, .. } => {
 863                thread.update(cx, |thread, cx| thread.cancel_last_completion(window, cx));
 864            }
 865            ActiveView::ExternalAgentThread { .. }
 866            | ActiveView::TextThread { .. }
 867            | ActiveView::History
 868            | ActiveView::Configuration => {}
 869        }
 870    }
 871
 872    fn active_message_editor(&self) -> Option<&Entity<MessageEditor>> {
 873        match &self.active_view {
 874            ActiveView::Thread { message_editor, .. } => Some(message_editor),
 875            ActiveView::ExternalAgentThread { .. }
 876            | ActiveView::TextThread { .. }
 877            | ActiveView::History
 878            | ActiveView::Configuration => None,
 879        }
 880    }
 881
 882    fn new_thread(&mut self, action: &NewThread, window: &mut Window, cx: &mut Context<Self>) {
 883        if cx.has_flag::<GeminiAndNativeFeatureFlag>() {
 884            return self.new_agent_thread(AgentType::NativeAgent, window, cx);
 885        }
 886        // Preserve chat box text when using creating new thread
 887        let preserved_text = self
 888            .active_message_editor()
 889            .map(|editor| editor.read(cx).get_text(cx).trim().to_string());
 890
 891        let thread = self
 892            .thread_store
 893            .update(cx, |this, cx| this.create_thread(cx));
 894
 895        let context_store = cx.new(|_cx| {
 896            ContextStore::new(
 897                self.project.downgrade(),
 898                Some(self.thread_store.downgrade()),
 899            )
 900        });
 901
 902        if let Some(other_thread_id) = action.from_thread_id.clone() {
 903            let other_thread_task = self.thread_store.update(cx, |this, cx| {
 904                this.open_thread(&other_thread_id, window, cx)
 905            });
 906
 907            cx.spawn({
 908                let context_store = context_store.clone();
 909
 910                async move |_panel, cx| {
 911                    let other_thread = other_thread_task.await?;
 912
 913                    context_store.update(cx, |this, cx| {
 914                        this.add_thread(other_thread, false, cx);
 915                    })?;
 916                    anyhow::Ok(())
 917                }
 918            })
 919            .detach_and_log_err(cx);
 920        }
 921
 922        let active_thread = cx.new(|cx| {
 923            ActiveThread::new(
 924                thread.clone(),
 925                self.thread_store.clone(),
 926                self.context_store.clone(),
 927                context_store.clone(),
 928                self.language_registry.clone(),
 929                self.workspace.clone(),
 930                window,
 931                cx,
 932            )
 933        });
 934
 935        let message_editor = cx.new(|cx| {
 936            MessageEditor::new(
 937                self.fs.clone(),
 938                self.workspace.clone(),
 939                context_store.clone(),
 940                self.prompt_store.clone(),
 941                self.thread_store.downgrade(),
 942                self.context_store.downgrade(),
 943                Some(self.history_store.downgrade()),
 944                thread.clone(),
 945                window,
 946                cx,
 947            )
 948        });
 949
 950        if let Some(text) = preserved_text {
 951            message_editor.update(cx, |editor, cx| {
 952                editor.set_text(text, window, cx);
 953            });
 954        }
 955
 956        message_editor.focus_handle(cx).focus(window);
 957
 958        let thread_view = ActiveView::thread(active_thread.clone(), message_editor, window, cx);
 959        self.set_active_view(thread_view, window, cx);
 960
 961        AgentDiff::set_active_thread(&self.workspace, thread.clone(), window, cx);
 962    }
 963
 964    fn new_prompt_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 965        let context = self
 966            .context_store
 967            .update(cx, |context_store, cx| context_store.create(cx));
 968        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
 969            .log_err()
 970            .flatten();
 971
 972        let context_editor = cx.new(|cx| {
 973            let mut editor = TextThreadEditor::for_context(
 974                context,
 975                self.fs.clone(),
 976                self.workspace.clone(),
 977                self.project.clone(),
 978                lsp_adapter_delegate,
 979                window,
 980                cx,
 981            );
 982            editor.insert_default_prompt(window, cx);
 983            editor
 984        });
 985
 986        self.set_active_view(
 987            ActiveView::prompt_editor(
 988                context_editor.clone(),
 989                self.history_store.clone(),
 990                self.acp_history_store.clone(),
 991                self.language_registry.clone(),
 992                window,
 993                cx,
 994            ),
 995            window,
 996            cx,
 997        );
 998        context_editor.focus_handle(cx).focus(window);
 999    }
1000
1001    fn external_thread(
1002        &mut self,
1003        agent_choice: Option<crate::ExternalAgent>,
1004        resume_thread: Option<DbThreadMetadata>,
1005        window: &mut Window,
1006        cx: &mut Context<Self>,
1007    ) {
1008        let workspace = self.workspace.clone();
1009        let project = self.project.clone();
1010        let fs = self.fs.clone();
1011
1012        const LAST_USED_EXTERNAL_AGENT_KEY: &str = "agent_panel__last_used_external_agent";
1013
1014        #[derive(Default, Serialize, Deserialize)]
1015        struct LastUsedExternalAgent {
1016            agent: crate::ExternalAgent,
1017        }
1018
1019        let thread_store = self.thread_store.clone();
1020        let text_thread_store = self.context_store.clone();
1021        let history = self.acp_history_store.clone();
1022
1023        cx.spawn_in(window, async move |this, cx| {
1024            let ext_agent = match agent_choice {
1025                Some(agent) => {
1026                    cx.background_spawn(async move {
1027                        if let Some(serialized) =
1028                            serde_json::to_string(&LastUsedExternalAgent { agent }).log_err()
1029                        {
1030                            KEY_VALUE_STORE
1031                                .write_kvp(LAST_USED_EXTERNAL_AGENT_KEY.to_string(), serialized)
1032                                .await
1033                                .log_err();
1034                        }
1035                    })
1036                    .detach();
1037
1038                    agent
1039                }
1040                None => {
1041                    cx.background_spawn(async move {
1042                        KEY_VALUE_STORE.read_kvp(LAST_USED_EXTERNAL_AGENT_KEY)
1043                    })
1044                    .await
1045                    .log_err()
1046                    .flatten()
1047                    .and_then(|value| {
1048                        serde_json::from_str::<LastUsedExternalAgent>(&value).log_err()
1049                    })
1050                    .unwrap_or_default()
1051                    .agent
1052                }
1053            };
1054
1055            let server = ext_agent.server(fs, history);
1056
1057            this.update_in(cx, |this, window, cx| {
1058                match ext_agent {
1059                    crate::ExternalAgent::Gemini | crate::ExternalAgent::NativeAgent => {
1060                        if !cx.has_flag::<GeminiAndNativeFeatureFlag>() {
1061                            return;
1062                        }
1063                    }
1064                    crate::ExternalAgent::ClaudeCode => {
1065                        if !cx.has_flag::<ClaudeCodeFeatureFlag>() {
1066                            return;
1067                        }
1068                    }
1069                }
1070
1071                let thread_view = cx.new(|cx| {
1072                    crate::acp::AcpThreadView::new(
1073                        server,
1074                        resume_thread,
1075                        workspace.clone(),
1076                        project,
1077                        this.acp_history_store.clone(),
1078                        thread_store.clone(),
1079                        text_thread_store.clone(),
1080                        window,
1081                        cx,
1082                    )
1083                });
1084
1085                this.set_active_view(ActiveView::ExternalAgentThread { thread_view }, window, cx);
1086            })
1087        })
1088        .detach_and_log_err(cx);
1089    }
1090
1091    fn deploy_rules_library(
1092        &mut self,
1093        action: &OpenRulesLibrary,
1094        _window: &mut Window,
1095        cx: &mut Context<Self>,
1096    ) {
1097        open_rules_library(
1098            self.language_registry.clone(),
1099            Box::new(PromptLibraryInlineAssist::new(self.workspace.clone())),
1100            Rc::new(|| {
1101                Rc::new(SlashCommandCompletionProvider::new(
1102                    Arc::new(SlashCommandWorkingSet::default()),
1103                    None,
1104                    None,
1105                ))
1106            }),
1107            action
1108                .prompt_to_select
1109                .map(|uuid| UserPromptId(uuid).into()),
1110            cx,
1111        )
1112        .detach_and_log_err(cx);
1113    }
1114
1115    fn open_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1116        if matches!(self.active_view, ActiveView::History) {
1117            if let Some(previous_view) = self.previous_view.take() {
1118                self.set_active_view(previous_view, window, cx);
1119            }
1120        } else {
1121            self.thread_store
1122                .update(cx, |thread_store, cx| thread_store.reload(cx))
1123                .detach_and_log_err(cx);
1124            self.set_active_view(ActiveView::History, window, cx);
1125        }
1126        cx.notify();
1127    }
1128
1129    pub(crate) fn open_saved_prompt_editor(
1130        &mut self,
1131        path: Arc<Path>,
1132        window: &mut Window,
1133        cx: &mut Context<Self>,
1134    ) -> Task<Result<()>> {
1135        let context = self
1136            .context_store
1137            .update(cx, |store, cx| store.open_local_context(path, cx));
1138        cx.spawn_in(window, async move |this, cx| {
1139            let context = context.await?;
1140            this.update_in(cx, |this, window, cx| {
1141                this.open_prompt_editor(context, window, cx);
1142            })
1143        })
1144    }
1145
1146    pub(crate) fn open_prompt_editor(
1147        &mut self,
1148        context: Entity<AssistantContext>,
1149        window: &mut Window,
1150        cx: &mut Context<Self>,
1151    ) {
1152        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project.clone(), cx)
1153            .log_err()
1154            .flatten();
1155        let editor = cx.new(|cx| {
1156            TextThreadEditor::for_context(
1157                context,
1158                self.fs.clone(),
1159                self.workspace.clone(),
1160                self.project.clone(),
1161                lsp_adapter_delegate,
1162                window,
1163                cx,
1164            )
1165        });
1166        self.set_active_view(
1167            ActiveView::prompt_editor(
1168                editor.clone(),
1169                self.history_store.clone(),
1170                self.acp_history_store.clone(),
1171                self.language_registry.clone(),
1172                window,
1173                cx,
1174            ),
1175            window,
1176            cx,
1177        );
1178    }
1179
1180    pub(crate) fn open_thread_by_id(
1181        &mut self,
1182        thread_id: &ThreadId,
1183        window: &mut Window,
1184        cx: &mut Context<Self>,
1185    ) -> Task<Result<()>> {
1186        let open_thread_task = self
1187            .thread_store
1188            .update(cx, |this, cx| this.open_thread(thread_id, window, cx));
1189        cx.spawn_in(window, async move |this, cx| {
1190            let thread = open_thread_task.await?;
1191            this.update_in(cx, |this, window, cx| {
1192                this.open_thread(thread, window, cx);
1193                anyhow::Ok(())
1194            })??;
1195            Ok(())
1196        })
1197    }
1198
1199    pub(crate) fn open_thread(
1200        &mut self,
1201        thread: Entity<Thread>,
1202        window: &mut Window,
1203        cx: &mut Context<Self>,
1204    ) {
1205        let context_store = cx.new(|_cx| {
1206            ContextStore::new(
1207                self.project.downgrade(),
1208                Some(self.thread_store.downgrade()),
1209            )
1210        });
1211
1212        let active_thread = cx.new(|cx| {
1213            ActiveThread::new(
1214                thread.clone(),
1215                self.thread_store.clone(),
1216                self.context_store.clone(),
1217                context_store.clone(),
1218                self.language_registry.clone(),
1219                self.workspace.clone(),
1220                window,
1221                cx,
1222            )
1223        });
1224
1225        let message_editor = cx.new(|cx| {
1226            MessageEditor::new(
1227                self.fs.clone(),
1228                self.workspace.clone(),
1229                context_store,
1230                self.prompt_store.clone(),
1231                self.thread_store.downgrade(),
1232                self.context_store.downgrade(),
1233                Some(self.history_store.downgrade()),
1234                thread.clone(),
1235                window,
1236                cx,
1237            )
1238        });
1239        message_editor.focus_handle(cx).focus(window);
1240
1241        let thread_view = ActiveView::thread(active_thread.clone(), message_editor, window, cx);
1242        self.set_active_view(thread_view, window, cx);
1243        AgentDiff::set_active_thread(&self.workspace, thread.clone(), window, cx);
1244    }
1245
1246    pub fn go_back(&mut self, _: &workspace::GoBack, window: &mut Window, cx: &mut Context<Self>) {
1247        match self.active_view {
1248            ActiveView::Configuration | ActiveView::History => {
1249                if let Some(previous_view) = self.previous_view.take() {
1250                    self.active_view = previous_view;
1251
1252                    match &self.active_view {
1253                        ActiveView::Thread { message_editor, .. } => {
1254                            message_editor.focus_handle(cx).focus(window);
1255                        }
1256                        ActiveView::ExternalAgentThread { thread_view } => {
1257                            thread_view.focus_handle(cx).focus(window);
1258                        }
1259                        ActiveView::TextThread { context_editor, .. } => {
1260                            context_editor.focus_handle(cx).focus(window);
1261                        }
1262                        ActiveView::History | ActiveView::Configuration => {}
1263                    }
1264                }
1265                cx.notify();
1266            }
1267            _ => {}
1268        }
1269    }
1270
1271    pub fn toggle_navigation_menu(
1272        &mut self,
1273        _: &ToggleNavigationMenu,
1274        window: &mut Window,
1275        cx: &mut Context<Self>,
1276    ) {
1277        self.assistant_navigation_menu_handle.toggle(window, cx);
1278    }
1279
1280    pub fn toggle_options_menu(
1281        &mut self,
1282        _: &ToggleOptionsMenu,
1283        window: &mut Window,
1284        cx: &mut Context<Self>,
1285    ) {
1286        self.agent_panel_menu_handle.toggle(window, cx);
1287    }
1288
1289    pub fn toggle_new_thread_menu(
1290        &mut self,
1291        _: &ToggleNewThreadMenu,
1292        window: &mut Window,
1293        cx: &mut Context<Self>,
1294    ) {
1295        self.new_thread_menu_handle.toggle(window, cx);
1296    }
1297
1298    pub fn increase_font_size(
1299        &mut self,
1300        action: &IncreaseBufferFontSize,
1301        _: &mut Window,
1302        cx: &mut Context<Self>,
1303    ) {
1304        self.handle_font_size_action(action.persist, px(1.0), cx);
1305    }
1306
1307    pub fn decrease_font_size(
1308        &mut self,
1309        action: &DecreaseBufferFontSize,
1310        _: &mut Window,
1311        cx: &mut Context<Self>,
1312    ) {
1313        self.handle_font_size_action(action.persist, px(-1.0), cx);
1314    }
1315
1316    fn handle_font_size_action(&mut self, persist: bool, delta: Pixels, cx: &mut Context<Self>) {
1317        match self.active_view.which_font_size_used() {
1318            WhichFontSize::AgentFont => {
1319                if persist {
1320                    update_settings_file::<ThemeSettings>(
1321                        self.fs.clone(),
1322                        cx,
1323                        move |settings, cx| {
1324                            let agent_font_size =
1325                                ThemeSettings::get_global(cx).agent_font_size(cx) + delta;
1326                            let _ = settings
1327                                .agent_font_size
1328                                .insert(Some(theme::clamp_font_size(agent_font_size).into()));
1329                        },
1330                    );
1331                } else {
1332                    theme::adjust_agent_font_size(cx, |size| size + delta);
1333                }
1334            }
1335            WhichFontSize::BufferFont => {
1336                // Prompt editor uses the buffer font size, so allow the action to propagate to the
1337                // default handler that changes that font size.
1338                cx.propagate();
1339            }
1340            WhichFontSize::None => {}
1341        }
1342    }
1343
1344    pub fn reset_font_size(
1345        &mut self,
1346        action: &ResetBufferFontSize,
1347        _: &mut Window,
1348        cx: &mut Context<Self>,
1349    ) {
1350        if action.persist {
1351            update_settings_file::<ThemeSettings>(self.fs.clone(), cx, move |settings, _| {
1352                settings.agent_font_size = None;
1353            });
1354        } else {
1355            theme::reset_agent_font_size(cx);
1356        }
1357    }
1358
1359    pub fn toggle_zoom(&mut self, _: &ToggleZoom, window: &mut Window, cx: &mut Context<Self>) {
1360        if self.zoomed {
1361            cx.emit(PanelEvent::ZoomOut);
1362        } else {
1363            if !self.focus_handle(cx).contains_focused(window, cx) {
1364                cx.focus_self(window);
1365            }
1366            cx.emit(PanelEvent::ZoomIn);
1367        }
1368    }
1369
1370    pub fn open_agent_diff(
1371        &mut self,
1372        _: &OpenAgentDiff,
1373        window: &mut Window,
1374        cx: &mut Context<Self>,
1375    ) {
1376        match &self.active_view {
1377            ActiveView::Thread { thread, .. } => {
1378                let thread = thread.read(cx).thread().clone();
1379                self.workspace
1380                    .update(cx, |workspace, cx| {
1381                        AgentDiffPane::deploy_in_workspace(
1382                            AgentDiffThread::Native(thread),
1383                            workspace,
1384                            window,
1385                            cx,
1386                        )
1387                    })
1388                    .log_err();
1389            }
1390            ActiveView::ExternalAgentThread { .. }
1391            | ActiveView::TextThread { .. }
1392            | ActiveView::History
1393            | ActiveView::Configuration => {}
1394        }
1395    }
1396
1397    pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1398        let context_server_store = self.project.read(cx).context_server_store();
1399        let tools = self.thread_store.read(cx).tools();
1400        let fs = self.fs.clone();
1401
1402        self.set_active_view(ActiveView::Configuration, window, cx);
1403        self.configuration = Some(cx.new(|cx| {
1404            AgentConfiguration::new(
1405                fs,
1406                context_server_store,
1407                tools,
1408                self.language_registry.clone(),
1409                self.workspace.clone(),
1410                window,
1411                cx,
1412            )
1413        }));
1414
1415        if let Some(configuration) = self.configuration.as_ref() {
1416            self.configuration_subscription = Some(cx.subscribe_in(
1417                configuration,
1418                window,
1419                Self::handle_agent_configuration_event,
1420            ));
1421
1422            configuration.focus_handle(cx).focus(window);
1423        }
1424    }
1425
1426    pub(crate) fn open_active_thread_as_markdown(
1427        &mut self,
1428        _: &OpenActiveThreadAsMarkdown,
1429        window: &mut Window,
1430        cx: &mut Context<Self>,
1431    ) {
1432        let Some(workspace) = self.workspace.upgrade() else {
1433            return;
1434        };
1435
1436        match &self.active_view {
1437            ActiveView::Thread { thread, .. } => {
1438                active_thread::open_active_thread_as_markdown(
1439                    thread.read(cx).thread().clone(),
1440                    workspace,
1441                    window,
1442                    cx,
1443                )
1444                .detach_and_log_err(cx);
1445            }
1446            ActiveView::ExternalAgentThread { thread_view } => {
1447                thread_view
1448                    .update(cx, |thread_view, cx| {
1449                        thread_view.open_thread_as_markdown(workspace, window, cx)
1450                    })
1451                    .detach_and_log_err(cx);
1452            }
1453            ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {}
1454        }
1455    }
1456
1457    fn handle_agent_configuration_event(
1458        &mut self,
1459        _entity: &Entity<AgentConfiguration>,
1460        event: &AssistantConfigurationEvent,
1461        window: &mut Window,
1462        cx: &mut Context<Self>,
1463    ) {
1464        match event {
1465            AssistantConfigurationEvent::NewThread(provider) => {
1466                if LanguageModelRegistry::read_global(cx)
1467                    .default_model()
1468                    .is_none_or(|model| model.provider.id() != provider.id())
1469                    && let Some(model) = provider.default_model(cx)
1470                {
1471                    update_settings_file::<AgentSettings>(
1472                        self.fs.clone(),
1473                        cx,
1474                        move |settings, _| settings.set_model(model),
1475                    );
1476                }
1477
1478                self.new_thread(&NewThread::default(), window, cx);
1479                if let Some((thread, model)) =
1480                    self.active_thread(cx).zip(provider.default_model(cx))
1481                {
1482                    thread.update(cx, |thread, cx| {
1483                        thread.set_configured_model(
1484                            Some(ConfiguredModel {
1485                                provider: provider.clone(),
1486                                model,
1487                            }),
1488                            cx,
1489                        );
1490                    });
1491                }
1492            }
1493        }
1494    }
1495
1496    pub(crate) fn active_thread(&self, cx: &App) -> Option<Entity<Thread>> {
1497        match &self.active_view {
1498            ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
1499            _ => None,
1500        }
1501    }
1502
1503    pub(crate) fn delete_thread(
1504        &mut self,
1505        thread_id: &ThreadId,
1506        cx: &mut Context<Self>,
1507    ) -> Task<Result<()>> {
1508        self.thread_store
1509            .update(cx, |this, cx| this.delete_thread(thread_id, cx))
1510    }
1511
1512    fn continue_conversation(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1513        let ActiveView::Thread { thread, .. } = &self.active_view else {
1514            return;
1515        };
1516
1517        let thread_state = thread.read(cx).thread().read(cx);
1518        if !thread_state.tool_use_limit_reached() {
1519            return;
1520        }
1521
1522        let model = thread_state.configured_model().map(|cm| cm.model.clone());
1523        if let Some(model) = model {
1524            thread.update(cx, |active_thread, cx| {
1525                active_thread.thread().update(cx, |thread, cx| {
1526                    thread.insert_invisible_continue_message(cx);
1527                    thread.advance_prompt_id();
1528                    thread.send_to_model(
1529                        model,
1530                        CompletionIntent::UserPrompt,
1531                        Some(window.window_handle()),
1532                        cx,
1533                    );
1534                });
1535            });
1536        } else {
1537            log::warn!("No configured model available for continuation");
1538        }
1539    }
1540
1541    fn toggle_burn_mode(
1542        &mut self,
1543        _: &ToggleBurnMode,
1544        _window: &mut Window,
1545        cx: &mut Context<Self>,
1546    ) {
1547        let ActiveView::Thread { thread, .. } = &self.active_view else {
1548            return;
1549        };
1550
1551        thread.update(cx, |active_thread, cx| {
1552            active_thread.thread().update(cx, |thread, _cx| {
1553                let current_mode = thread.completion_mode();
1554
1555                thread.set_completion_mode(match current_mode {
1556                    CompletionMode::Burn => CompletionMode::Normal,
1557                    CompletionMode::Normal => CompletionMode::Burn,
1558                });
1559            });
1560        });
1561    }
1562
1563    pub(crate) fn active_context_editor(&self) -> Option<Entity<TextThreadEditor>> {
1564        match &self.active_view {
1565            ActiveView::TextThread { context_editor, .. } => Some(context_editor.clone()),
1566            _ => None,
1567        }
1568    }
1569
1570    pub(crate) fn delete_context(
1571        &mut self,
1572        path: Arc<Path>,
1573        cx: &mut Context<Self>,
1574    ) -> Task<Result<()>> {
1575        self.context_store
1576            .update(cx, |this, cx| this.delete_local_context(path, cx))
1577    }
1578
1579    fn set_active_view(
1580        &mut self,
1581        new_view: ActiveView,
1582        window: &mut Window,
1583        cx: &mut Context<Self>,
1584    ) {
1585        let current_is_history = matches!(self.active_view, ActiveView::History);
1586        let new_is_history = matches!(new_view, ActiveView::History);
1587
1588        let current_is_config = matches!(self.active_view, ActiveView::Configuration);
1589        let new_is_config = matches!(new_view, ActiveView::Configuration);
1590
1591        let current_is_special = current_is_history || current_is_config;
1592        let new_is_special = new_is_history || new_is_config;
1593
1594        if let ActiveView::Thread { thread, .. } = &self.active_view {
1595            let thread = thread.read(cx);
1596            if thread.is_empty() {
1597                let id = thread.thread().read(cx).id().clone();
1598                self.history_store.update(cx, |store, cx| {
1599                    store.remove_recently_opened_thread(id, cx);
1600                });
1601            }
1602        }
1603
1604        match &new_view {
1605            ActiveView::Thread { thread, .. } => self.history_store.update(cx, |store, cx| {
1606                let id = thread.read(cx).thread().read(cx).id().clone();
1607                store.push_recently_opened_entry(HistoryEntryId::Thread(id), cx);
1608            }),
1609            ActiveView::TextThread { context_editor, .. } => {
1610                self.history_store.update(cx, |store, cx| {
1611                    if let Some(path) = context_editor.read(cx).context().read(cx).path() {
1612                        store.push_recently_opened_entry(HistoryEntryId::Context(path.clone()), cx)
1613                    }
1614                });
1615                self.acp_history_store.update(cx, |store, cx| {
1616                    if let Some(path) = context_editor.read(cx).context().read(cx).path() {
1617                        store.push_recently_opened_entry(
1618                            agent2::HistoryEntryId::TextThread(path.clone()),
1619                            cx,
1620                        )
1621                    }
1622                })
1623            }
1624            ActiveView::ExternalAgentThread { .. } => {}
1625            ActiveView::History | ActiveView::Configuration => {}
1626        }
1627
1628        if current_is_special && !new_is_special {
1629            self.active_view = new_view;
1630        } else if !current_is_special && new_is_special {
1631            self.previous_view = Some(std::mem::replace(&mut self.active_view, new_view));
1632        } else {
1633            if !new_is_special {
1634                self.previous_view = None;
1635            }
1636            self.active_view = new_view;
1637        }
1638
1639        self.focus_handle(cx).focus(window);
1640    }
1641
1642    fn populate_recently_opened_menu_section_old(
1643        mut menu: ContextMenu,
1644        panel: Entity<Self>,
1645        cx: &mut Context<ContextMenu>,
1646    ) -> ContextMenu {
1647        let entries = panel
1648            .read(cx)
1649            .history_store
1650            .read(cx)
1651            .recently_opened_entries(cx);
1652
1653        if entries.is_empty() {
1654            return menu;
1655        }
1656
1657        menu = menu.header("Recently Opened");
1658
1659        for entry in entries {
1660            let title = entry.title().clone();
1661            let id = entry.id();
1662
1663            menu = menu.entry_with_end_slot_on_hover(
1664                title,
1665                None,
1666                {
1667                    let panel = panel.downgrade();
1668                    let id = id.clone();
1669                    move |window, cx| {
1670                        let id = id.clone();
1671                        panel
1672                            .update(cx, move |this, cx| match id {
1673                                HistoryEntryId::Thread(id) => this
1674                                    .open_thread_by_id(&id, window, cx)
1675                                    .detach_and_log_err(cx),
1676                                HistoryEntryId::Context(path) => this
1677                                    .open_saved_prompt_editor(path.clone(), window, cx)
1678                                    .detach_and_log_err(cx),
1679                            })
1680                            .ok();
1681                    }
1682                },
1683                IconName::Close,
1684                "Close Entry".into(),
1685                {
1686                    let panel = panel.downgrade();
1687                    let id = id.clone();
1688                    move |_window, cx| {
1689                        panel
1690                            .update(cx, |this, cx| {
1691                                this.history_store.update(cx, |history_store, cx| {
1692                                    history_store.remove_recently_opened_entry(&id, cx);
1693                                });
1694                            })
1695                            .ok();
1696                    }
1697                },
1698            );
1699        }
1700
1701        menu = menu.separator();
1702
1703        menu
1704    }
1705
1706    fn populate_recently_opened_menu_section_new(
1707        mut menu: ContextMenu,
1708        panel: Entity<Self>,
1709        cx: &mut Context<ContextMenu>,
1710    ) -> ContextMenu {
1711        let entries = panel
1712            .read(cx)
1713            .acp_history_store
1714            .read(cx)
1715            .recently_opened_entries(cx);
1716
1717        if entries.is_empty() {
1718            return menu;
1719        }
1720
1721        menu = menu.header("Recently Opened");
1722
1723        for entry in entries {
1724            let title = entry.title().clone();
1725
1726            menu = menu.entry_with_end_slot_on_hover(
1727                title,
1728                None,
1729                {
1730                    let panel = panel.downgrade();
1731                    let entry = entry.clone();
1732                    move |window, cx| {
1733                        let entry = entry.clone();
1734                        panel
1735                            .update(cx, move |this, cx| match &entry {
1736                                agent2::HistoryEntry::AcpThread(entry) => this.external_thread(
1737                                    Some(ExternalAgent::NativeAgent),
1738                                    Some(entry.clone()),
1739                                    window,
1740                                    cx,
1741                                ),
1742                                agent2::HistoryEntry::TextThread(entry) => this
1743                                    .open_saved_prompt_editor(entry.path.clone(), window, cx)
1744                                    .detach_and_log_err(cx),
1745                            })
1746                            .ok();
1747                    }
1748                },
1749                IconName::Close,
1750                "Close Entry".into(),
1751                {
1752                    let panel = panel.downgrade();
1753                    let id = entry.id();
1754                    move |_window, cx| {
1755                        panel
1756                            .update(cx, |this, cx| {
1757                                this.acp_history_store.update(cx, |history_store, cx| {
1758                                    history_store.remove_recently_opened_entry(&id, cx);
1759                                });
1760                            })
1761                            .ok();
1762                    }
1763                },
1764            );
1765        }
1766
1767        menu = menu.separator();
1768
1769        menu
1770    }
1771
1772    pub fn set_selected_agent(
1773        &mut self,
1774        agent: AgentType,
1775        window: &mut Window,
1776        cx: &mut Context<Self>,
1777    ) {
1778        if self.selected_agent != agent {
1779            self.selected_agent = agent;
1780            self.serialize(cx);
1781        }
1782        self.new_agent_thread(agent, window, cx);
1783    }
1784
1785    pub fn selected_agent(&self) -> AgentType {
1786        self.selected_agent
1787    }
1788
1789    pub fn new_agent_thread(
1790        &mut self,
1791        agent: AgentType,
1792        window: &mut Window,
1793        cx: &mut Context<Self>,
1794    ) {
1795        match agent {
1796            AgentType::Zed => {
1797                window.dispatch_action(
1798                    NewThread {
1799                        from_thread_id: None,
1800                    }
1801                    .boxed_clone(),
1802                    cx,
1803                );
1804            }
1805            AgentType::TextThread => {
1806                window.dispatch_action(NewTextThread.boxed_clone(), cx);
1807            }
1808            AgentType::NativeAgent => {
1809                self.external_thread(Some(crate::ExternalAgent::NativeAgent), None, window, cx)
1810            }
1811            AgentType::Gemini => {
1812                self.external_thread(Some(crate::ExternalAgent::Gemini), None, window, cx)
1813            }
1814            AgentType::ClaudeCode => {
1815                self.external_thread(Some(crate::ExternalAgent::ClaudeCode), None, window, cx)
1816            }
1817        }
1818    }
1819}
1820
1821impl Focusable for AgentPanel {
1822    fn focus_handle(&self, cx: &App) -> FocusHandle {
1823        match &self.active_view {
1824            ActiveView::Thread { message_editor, .. } => message_editor.focus_handle(cx),
1825            ActiveView::ExternalAgentThread { thread_view, .. } => thread_view.focus_handle(cx),
1826            ActiveView::History => {
1827                if cx.has_flag::<feature_flags::GeminiAndNativeFeatureFlag>() {
1828                    self.acp_history.focus_handle(cx)
1829                } else {
1830                    self.history.focus_handle(cx)
1831                }
1832            }
1833            ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx),
1834            ActiveView::Configuration => {
1835                if let Some(configuration) = self.configuration.as_ref() {
1836                    configuration.focus_handle(cx)
1837                } else {
1838                    cx.focus_handle()
1839                }
1840            }
1841        }
1842    }
1843}
1844
1845fn agent_panel_dock_position(cx: &App) -> DockPosition {
1846    match AgentSettings::get_global(cx).dock {
1847        AgentDockPosition::Left => DockPosition::Left,
1848        AgentDockPosition::Bottom => DockPosition::Bottom,
1849        AgentDockPosition::Right => DockPosition::Right,
1850    }
1851}
1852
1853impl EventEmitter<PanelEvent> for AgentPanel {}
1854
1855impl Panel for AgentPanel {
1856    fn persistent_name() -> &'static str {
1857        "AgentPanel"
1858    }
1859
1860    fn position(&self, _window: &Window, cx: &App) -> DockPosition {
1861        agent_panel_dock_position(cx)
1862    }
1863
1864    fn position_is_valid(&self, position: DockPosition) -> bool {
1865        position != DockPosition::Bottom
1866    }
1867
1868    fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
1869        settings::update_settings_file::<AgentSettings>(self.fs.clone(), cx, move |settings, _| {
1870            let dock = match position {
1871                DockPosition::Left => AgentDockPosition::Left,
1872                DockPosition::Bottom => AgentDockPosition::Bottom,
1873                DockPosition::Right => AgentDockPosition::Right,
1874            };
1875            settings.set_dock(dock);
1876        });
1877    }
1878
1879    fn size(&self, window: &Window, cx: &App) -> Pixels {
1880        let settings = AgentSettings::get_global(cx);
1881        match self.position(window, cx) {
1882            DockPosition::Left | DockPosition::Right => {
1883                self.width.unwrap_or(settings.default_width)
1884            }
1885            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
1886        }
1887    }
1888
1889    fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
1890        match self.position(window, cx) {
1891            DockPosition::Left | DockPosition::Right => self.width = size,
1892            DockPosition::Bottom => self.height = size,
1893        }
1894        self.serialize(cx);
1895        cx.notify();
1896    }
1897
1898    fn set_active(&mut self, _active: bool, _window: &mut Window, _cx: &mut Context<Self>) {}
1899
1900    fn remote_id() -> Option<proto::PanelId> {
1901        Some(proto::PanelId::AssistantPanel)
1902    }
1903
1904    fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
1905        (self.enabled(cx) && AgentSettings::get_global(cx).button).then_some(IconName::ZedAssistant)
1906    }
1907
1908    fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
1909        Some("Agent Panel")
1910    }
1911
1912    fn toggle_action(&self) -> Box<dyn Action> {
1913        Box::new(ToggleFocus)
1914    }
1915
1916    fn activation_priority(&self) -> u32 {
1917        3
1918    }
1919
1920    fn enabled(&self, cx: &App) -> bool {
1921        DisableAiSettings::get_global(cx).disable_ai.not() && AgentSettings::get_global(cx).enabled
1922    }
1923
1924    fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
1925        self.zoomed
1926    }
1927
1928    fn set_zoomed(&mut self, zoomed: bool, _window: &mut Window, cx: &mut Context<Self>) {
1929        self.zoomed = zoomed;
1930        cx.notify();
1931    }
1932}
1933
1934impl AgentPanel {
1935    fn render_title_view(&self, _window: &mut Window, cx: &Context<Self>) -> AnyElement {
1936        const LOADING_SUMMARY_PLACEHOLDER: &str = "Loading Summary…";
1937
1938        let content = match &self.active_view {
1939            ActiveView::Thread {
1940                thread: active_thread,
1941                change_title_editor,
1942                ..
1943            } => {
1944                let state = {
1945                    let active_thread = active_thread.read(cx);
1946                    if active_thread.is_empty() {
1947                        &ThreadSummary::Pending
1948                    } else {
1949                        active_thread.summary(cx)
1950                    }
1951                };
1952
1953                match state {
1954                    ThreadSummary::Pending => Label::new(ThreadSummary::DEFAULT.clone())
1955                        .truncate()
1956                        .into_any_element(),
1957                    ThreadSummary::Generating => Label::new(LOADING_SUMMARY_PLACEHOLDER)
1958                        .truncate()
1959                        .into_any_element(),
1960                    ThreadSummary::Ready(_) => div()
1961                        .w_full()
1962                        .child(change_title_editor.clone())
1963                        .into_any_element(),
1964                    ThreadSummary::Error => h_flex()
1965                        .w_full()
1966                        .child(change_title_editor.clone())
1967                        .child(
1968                            IconButton::new("retry-summary-generation", IconName::RotateCcw)
1969                                .icon_size(IconSize::Small)
1970                                .on_click({
1971                                    let active_thread = active_thread.clone();
1972                                    move |_, _window, cx| {
1973                                        active_thread.update(cx, |thread, cx| {
1974                                            thread.regenerate_summary(cx);
1975                                        });
1976                                    }
1977                                })
1978                                .tooltip(move |_window, cx| {
1979                                    cx.new(|_| {
1980                                        Tooltip::new("Failed to generate title")
1981                                            .meta("Click to try again")
1982                                    })
1983                                    .into()
1984                                }),
1985                        )
1986                        .into_any_element(),
1987                }
1988            }
1989            ActiveView::ExternalAgentThread { thread_view } => {
1990                Label::new(thread_view.read(cx).title(cx))
1991                    .truncate()
1992                    .into_any_element()
1993            }
1994            ActiveView::TextThread {
1995                title_editor,
1996                context_editor,
1997                ..
1998            } => {
1999                let summary = context_editor.read(cx).context().read(cx).summary();
2000
2001                match summary {
2002                    ContextSummary::Pending => Label::new(ContextSummary::DEFAULT)
2003                        .truncate()
2004                        .into_any_element(),
2005                    ContextSummary::Content(summary) => {
2006                        if summary.done {
2007                            div()
2008                                .w_full()
2009                                .child(title_editor.clone())
2010                                .into_any_element()
2011                        } else {
2012                            Label::new(LOADING_SUMMARY_PLACEHOLDER)
2013                                .truncate()
2014                                .into_any_element()
2015                        }
2016                    }
2017                    ContextSummary::Error => h_flex()
2018                        .w_full()
2019                        .child(title_editor.clone())
2020                        .child(
2021                            IconButton::new("retry-summary-generation", IconName::RotateCcw)
2022                                .icon_size(IconSize::Small)
2023                                .on_click({
2024                                    let context_editor = context_editor.clone();
2025                                    move |_, _window, cx| {
2026                                        context_editor.update(cx, |context_editor, cx| {
2027                                            context_editor.regenerate_summary(cx);
2028                                        });
2029                                    }
2030                                })
2031                                .tooltip(move |_window, cx| {
2032                                    cx.new(|_| {
2033                                        Tooltip::new("Failed to generate title")
2034                                            .meta("Click to try again")
2035                                    })
2036                                    .into()
2037                                }),
2038                        )
2039                        .into_any_element(),
2040                }
2041            }
2042            ActiveView::History => Label::new("History").truncate().into_any_element(),
2043            ActiveView::Configuration => Label::new("Settings").truncate().into_any_element(),
2044        };
2045
2046        h_flex()
2047            .key_context("TitleEditor")
2048            .id("TitleEditor")
2049            .flex_grow()
2050            .w_full()
2051            .max_w_full()
2052            .overflow_x_scroll()
2053            .child(content)
2054            .into_any()
2055    }
2056
2057    fn render_panel_options_menu(
2058        &self,
2059        window: &mut Window,
2060        cx: &mut Context<Self>,
2061    ) -> impl IntoElement {
2062        let user_store = self.user_store.read(cx);
2063        let usage = user_store.model_request_usage();
2064        let account_url = zed_urls::account_url(cx);
2065
2066        let focus_handle = self.focus_handle(cx);
2067
2068        let full_screen_label = if self.is_zoomed(window, cx) {
2069            "Disable Full Screen"
2070        } else {
2071            "Enable Full Screen"
2072        };
2073
2074        PopoverMenu::new("agent-options-menu")
2075            .trigger_with_tooltip(
2076                IconButton::new("agent-options-menu", IconName::Ellipsis)
2077                    .icon_size(IconSize::Small),
2078                {
2079                    let focus_handle = focus_handle.clone();
2080                    move |window, cx| {
2081                        Tooltip::for_action_in(
2082                            "Toggle Agent Menu",
2083                            &ToggleOptionsMenu,
2084                            &focus_handle,
2085                            window,
2086                            cx,
2087                        )
2088                    }
2089                },
2090            )
2091            .anchor(Corner::TopRight)
2092            .with_handle(self.agent_panel_menu_handle.clone())
2093            .menu({
2094                let focus_handle = focus_handle.clone();
2095                move |window, cx| {
2096                    Some(ContextMenu::build(window, cx, |mut menu, _window, _| {
2097                        menu = menu.context(focus_handle.clone());
2098                        if let Some(usage) = usage {
2099                            menu = menu
2100                                .header_with_link("Prompt Usage", "Manage", account_url.clone())
2101                                .custom_entry(
2102                                    move |_window, cx| {
2103                                        let used_percentage = match usage.limit {
2104                                            UsageLimit::Limited(limit) => {
2105                                                Some((usage.amount as f32 / limit as f32) * 100.)
2106                                            }
2107                                            UsageLimit::Unlimited => None,
2108                                        };
2109
2110                                        h_flex()
2111                                            .flex_1()
2112                                            .gap_1p5()
2113                                            .children(used_percentage.map(|percent| {
2114                                                ProgressBar::new("usage", percent, 100., cx)
2115                                            }))
2116                                            .child(
2117                                                Label::new(match usage.limit {
2118                                                    UsageLimit::Limited(limit) => {
2119                                                        format!("{} / {limit}", usage.amount)
2120                                                    }
2121                                                    UsageLimit::Unlimited => {
2122                                                        format!("{} / ∞", usage.amount)
2123                                                    }
2124                                                })
2125                                                .size(LabelSize::Small)
2126                                                .color(Color::Muted),
2127                                            )
2128                                            .into_any_element()
2129                                    },
2130                                    move |_, cx| cx.open_url(&zed_urls::account_url(cx)),
2131                                )
2132                                .separator()
2133                        }
2134
2135                        menu = menu
2136                            .header("MCP Servers")
2137                            .action(
2138                                "View Server Extensions",
2139                                Box::new(zed_actions::Extensions {
2140                                    category_filter: Some(
2141                                        zed_actions::ExtensionCategoryFilter::ContextServers,
2142                                    ),
2143                                    id: None,
2144                                }),
2145                            )
2146                            .action("Add Custom Server…", Box::new(AddContextServer))
2147                            .separator();
2148
2149                        menu = menu
2150                            .action("Rules…", Box::new(OpenRulesLibrary::default()))
2151                            .action("Settings", Box::new(OpenSettings))
2152                            .separator()
2153                            .action(full_screen_label, Box::new(ToggleZoom));
2154                        menu
2155                    }))
2156                }
2157            })
2158    }
2159
2160    fn render_recent_entries_menu(
2161        &self,
2162        icon: IconName,
2163        corner: Corner,
2164        cx: &mut Context<Self>,
2165    ) -> impl IntoElement {
2166        let focus_handle = self.focus_handle(cx);
2167
2168        PopoverMenu::new("agent-nav-menu")
2169            .trigger_with_tooltip(
2170                IconButton::new("agent-nav-menu", icon).icon_size(IconSize::Small),
2171                {
2172                    let focus_handle = focus_handle.clone();
2173                    move |window, cx| {
2174                        Tooltip::for_action_in(
2175                            "Toggle Recent Threads",
2176                            &ToggleNavigationMenu,
2177                            &focus_handle,
2178                            window,
2179                            cx,
2180                        )
2181                    }
2182                },
2183            )
2184            .anchor(corner)
2185            .with_handle(self.assistant_navigation_menu_handle.clone())
2186            .menu({
2187                let menu = self.assistant_navigation_menu.clone();
2188                move |window, cx| {
2189                    if let Some(menu) = menu.as_ref() {
2190                        menu.update(cx, |_, cx| {
2191                            cx.defer_in(window, |menu, window, cx| {
2192                                menu.rebuild(window, cx);
2193                            });
2194                        })
2195                    }
2196                    menu.clone()
2197                }
2198            })
2199    }
2200
2201    fn render_toolbar_back_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
2202        let focus_handle = self.focus_handle(cx);
2203
2204        IconButton::new("go-back", IconName::ArrowLeft)
2205            .icon_size(IconSize::Small)
2206            .on_click(cx.listener(|this, _, window, cx| {
2207                this.go_back(&workspace::GoBack, window, cx);
2208            }))
2209            .tooltip({
2210                let focus_handle = focus_handle.clone();
2211
2212                move |window, cx| {
2213                    Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, window, cx)
2214                }
2215            })
2216    }
2217
2218    fn render_toolbar_old(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2219        let focus_handle = self.focus_handle(cx);
2220
2221        let active_thread = match &self.active_view {
2222            ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
2223            ActiveView::ExternalAgentThread { .. }
2224            | ActiveView::TextThread { .. }
2225            | ActiveView::History
2226            | ActiveView::Configuration => None,
2227        };
2228
2229        let new_thread_menu = PopoverMenu::new("new_thread_menu")
2230            .trigger_with_tooltip(
2231                IconButton::new("new_thread_menu_btn", IconName::Plus).icon_size(IconSize::Small),
2232                Tooltip::text("New Thread…"),
2233            )
2234            .anchor(Corner::TopRight)
2235            .with_handle(self.new_thread_menu_handle.clone())
2236            .menu({
2237                let focus_handle = focus_handle.clone();
2238                move |window, cx| {
2239                    let active_thread = active_thread.clone();
2240                    Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
2241                        menu = menu
2242                            .context(focus_handle.clone())
2243                            .when_some(active_thread, |this, active_thread| {
2244                                let thread = active_thread.read(cx);
2245
2246                                if !thread.is_empty() {
2247                                    let thread_id = thread.id().clone();
2248                                    this.item(
2249                                        ContextMenuEntry::new("New From Summary")
2250                                            .icon(IconName::ThreadFromSummary)
2251                                            .icon_color(Color::Muted)
2252                                            .handler(move |window, cx| {
2253                                                window.dispatch_action(
2254                                                    Box::new(NewThread {
2255                                                        from_thread_id: Some(thread_id.clone()),
2256                                                    }),
2257                                                    cx,
2258                                                );
2259                                            }),
2260                                    )
2261                                } else {
2262                                    this
2263                                }
2264                            })
2265                            .item(
2266                                ContextMenuEntry::new("New Thread")
2267                                    .icon(IconName::Thread)
2268                                    .icon_color(Color::Muted)
2269                                    .action(NewThread::default().boxed_clone())
2270                                    .handler(move |window, cx| {
2271                                        window.dispatch_action(
2272                                            NewThread::default().boxed_clone(),
2273                                            cx,
2274                                        );
2275                                    }),
2276                            )
2277                            .item(
2278                                ContextMenuEntry::new("New Text Thread")
2279                                    .icon(IconName::TextThread)
2280                                    .icon_color(Color::Muted)
2281                                    .action(NewTextThread.boxed_clone())
2282                                    .handler(move |window, cx| {
2283                                        window.dispatch_action(NewTextThread.boxed_clone(), cx);
2284                                    }),
2285                            );
2286                        menu
2287                    }))
2288                }
2289            });
2290
2291        h_flex()
2292            .id("assistant-toolbar")
2293            .h(Tab::container_height(cx))
2294            .max_w_full()
2295            .flex_none()
2296            .justify_between()
2297            .gap_2()
2298            .bg(cx.theme().colors().tab_bar_background)
2299            .border_b_1()
2300            .border_color(cx.theme().colors().border)
2301            .child(
2302                h_flex()
2303                    .size_full()
2304                    .pl_1()
2305                    .gap_1()
2306                    .child(match &self.active_view {
2307                        ActiveView::History | ActiveView::Configuration => div()
2308                            .pl(DynamicSpacing::Base04.rems(cx))
2309                            .child(self.render_toolbar_back_button(cx))
2310                            .into_any_element(),
2311                        _ => self
2312                            .render_recent_entries_menu(IconName::MenuAlt, Corner::TopLeft, cx)
2313                            .into_any_element(),
2314                    })
2315                    .child(self.render_title_view(window, cx)),
2316            )
2317            .child(
2318                h_flex()
2319                    .h_full()
2320                    .gap_2()
2321                    .children(self.render_token_count(cx))
2322                    .child(
2323                        h_flex()
2324                            .h_full()
2325                            .gap(DynamicSpacing::Base02.rems(cx))
2326                            .px(DynamicSpacing::Base08.rems(cx))
2327                            .border_l_1()
2328                            .border_color(cx.theme().colors().border)
2329                            .child(new_thread_menu)
2330                            .child(self.render_panel_options_menu(window, cx)),
2331                    ),
2332            )
2333    }
2334
2335    fn render_toolbar_new(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2336        let focus_handle = self.focus_handle(cx);
2337
2338        let active_thread = match &self.active_view {
2339            ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
2340            ActiveView::ExternalAgentThread { .. }
2341            | ActiveView::TextThread { .. }
2342            | ActiveView::History
2343            | ActiveView::Configuration => None,
2344        };
2345
2346        let new_thread_menu = PopoverMenu::new("new_thread_menu")
2347            .trigger_with_tooltip(
2348                IconButton::new("new_thread_menu_btn", IconName::Plus).icon_size(IconSize::Small),
2349                {
2350                    let focus_handle = focus_handle.clone();
2351                    move |window, cx| {
2352                        Tooltip::for_action_in(
2353                            "New…",
2354                            &ToggleNewThreadMenu,
2355                            &focus_handle,
2356                            window,
2357                            cx,
2358                        )
2359                    }
2360                },
2361            )
2362            .anchor(Corner::TopLeft)
2363            .with_handle(self.new_thread_menu_handle.clone())
2364            .menu({
2365                let focus_handle = focus_handle.clone();
2366                let workspace = self.workspace.clone();
2367
2368                move |window, cx| {
2369                    let active_thread = active_thread.clone();
2370                    Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
2371                        menu = menu
2372                            .context(focus_handle.clone())
2373                            .header("Zed Agent")
2374                            .when_some(active_thread, |this, active_thread| {
2375                                let thread = active_thread.read(cx);
2376
2377                                if !thread.is_empty() {
2378                                    let thread_id = thread.id().clone();
2379                                    this.item(
2380                                        ContextMenuEntry::new("New From Summary")
2381                                            .icon(IconName::ThreadFromSummary)
2382                                            .icon_color(Color::Muted)
2383                                            .handler(move |window, cx| {
2384                                                window.dispatch_action(
2385                                                    Box::new(NewThread {
2386                                                        from_thread_id: Some(thread_id.clone()),
2387                                                    }),
2388                                                    cx,
2389                                                );
2390                                            }),
2391                                    )
2392                                } else {
2393                                    this
2394                                }
2395                            })
2396                            .item(
2397                                ContextMenuEntry::new("New Thread")
2398                                    .action(NewThread::default().boxed_clone())
2399                                    .icon(IconName::Thread)
2400                                    .icon_color(Color::Muted)
2401                                    .handler({
2402                                        let workspace = workspace.clone();
2403                                        move |window, cx| {
2404                                            if let Some(workspace) = workspace.upgrade() {
2405                                                workspace.update(cx, |workspace, cx| {
2406                                                    if let Some(panel) =
2407                                                        workspace.panel::<AgentPanel>(cx)
2408                                                    {
2409                                                        panel.update(cx, |panel, cx| {
2410                                                            panel.set_selected_agent(
2411                                                                AgentType::NativeAgent,
2412                                                                window,
2413                                                                cx,
2414                                                            );
2415                                                        });
2416                                                    }
2417                                                });
2418                                            }
2419                                        }
2420                                    }),
2421                            )
2422                            .item(
2423                                ContextMenuEntry::new("New Text Thread")
2424                                    .icon(IconName::TextThread)
2425                                    .icon_color(Color::Muted)
2426                                    .action(NewTextThread.boxed_clone())
2427                                    .handler({
2428                                        let workspace = workspace.clone();
2429                                        move |window, cx| {
2430                                            if let Some(workspace) = workspace.upgrade() {
2431                                                workspace.update(cx, |workspace, cx| {
2432                                                    if let Some(panel) =
2433                                                        workspace.panel::<AgentPanel>(cx)
2434                                                    {
2435                                                        panel.update(cx, |panel, cx| {
2436                                                            panel.set_selected_agent(
2437                                                                AgentType::TextThread,
2438                                                                window,
2439                                                                cx,
2440                                                            );
2441                                                        });
2442                                                    }
2443                                                });
2444                                            }
2445                                        }
2446                                    }),
2447                            )
2448                            .separator()
2449                            .header("External Agents")
2450                            .when(cx.has_flag::<GeminiAndNativeFeatureFlag>(), |menu| {
2451                                menu.item(
2452                                    ContextMenuEntry::new("New Gemini CLI Thread")
2453                                        .icon(IconName::AiGemini)
2454                                        .icon_color(Color::Muted)
2455                                        .handler({
2456                                            let workspace = workspace.clone();
2457                                            move |window, cx| {
2458                                                if let Some(workspace) = workspace.upgrade() {
2459                                                    workspace.update(cx, |workspace, cx| {
2460                                                        if let Some(panel) =
2461                                                            workspace.panel::<AgentPanel>(cx)
2462                                                        {
2463                                                            panel.update(cx, |panel, cx| {
2464                                                                panel.set_selected_agent(
2465                                                                    AgentType::Gemini,
2466                                                                    window,
2467                                                                    cx,
2468                                                                );
2469                                                            });
2470                                                        }
2471                                                    });
2472                                                }
2473                                            }
2474                                        }),
2475                                )
2476                            })
2477                            .when(cx.has_flag::<ClaudeCodeFeatureFlag>(), |menu| {
2478                                menu.item(
2479                                    ContextMenuEntry::new("New Claude Code Thread")
2480                                        .icon(IconName::AiClaude)
2481                                        .icon_color(Color::Muted)
2482                                        .handler({
2483                                            let workspace = workspace.clone();
2484                                            move |window, cx| {
2485                                                if let Some(workspace) = workspace.upgrade() {
2486                                                    workspace.update(cx, |workspace, cx| {
2487                                                        if let Some(panel) =
2488                                                            workspace.panel::<AgentPanel>(cx)
2489                                                        {
2490                                                            panel.update(cx, |panel, cx| {
2491                                                                panel.set_selected_agent(
2492                                                                    AgentType::ClaudeCode,
2493                                                                    window,
2494                                                                    cx,
2495                                                                );
2496                                                            });
2497                                                        }
2498                                                    });
2499                                                }
2500                                            }
2501                                        }),
2502                                )
2503                            });
2504                        menu
2505                    }))
2506                }
2507            });
2508
2509        let selected_agent_label = self.selected_agent.label().into();
2510        let selected_agent = div()
2511            .id("selected_agent_icon")
2512            .when_some(self.selected_agent.icon(), |this, icon| {
2513                this.px(DynamicSpacing::Base02.rems(cx))
2514                    .child(Icon::new(icon).color(Color::Muted))
2515                    .tooltip(move |window, cx| {
2516                        Tooltip::with_meta(
2517                            selected_agent_label.clone(),
2518                            None,
2519                            "Selected Agent",
2520                            window,
2521                            cx,
2522                        )
2523                    })
2524            })
2525            .into_any_element();
2526
2527        h_flex()
2528            .id("agent-panel-toolbar")
2529            .h(Tab::container_height(cx))
2530            .max_w_full()
2531            .flex_none()
2532            .justify_between()
2533            .gap_2()
2534            .bg(cx.theme().colors().tab_bar_background)
2535            .border_b_1()
2536            .border_color(cx.theme().colors().border)
2537            .child(
2538                h_flex()
2539                    .size_full()
2540                    .gap(DynamicSpacing::Base04.rems(cx))
2541                    .pl(DynamicSpacing::Base04.rems(cx))
2542                    .child(match &self.active_view {
2543                        ActiveView::History | ActiveView::Configuration => {
2544                            self.render_toolbar_back_button(cx).into_any_element()
2545                        }
2546                        _ => selected_agent.into_any_element(),
2547                    })
2548                    .child(self.render_title_view(window, cx)),
2549            )
2550            .child(
2551                h_flex()
2552                    .flex_none()
2553                    .gap(DynamicSpacing::Base02.rems(cx))
2554                    .pl(DynamicSpacing::Base04.rems(cx))
2555                    .pr(DynamicSpacing::Base06.rems(cx))
2556                    .child(new_thread_menu)
2557                    .child(self.render_recent_entries_menu(
2558                        IconName::MenuAltTemp,
2559                        Corner::TopRight,
2560                        cx,
2561                    ))
2562                    .child(self.render_panel_options_menu(window, cx)),
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}