agent_panel.rs

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