agent_panel.rs

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