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