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