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