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