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