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