agent_panel.rs

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