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, ElevationIndex, KeyBinding, PopoverMenu,
  72    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",
 250            Self::NativeAgent => "Agent 2",
 251            Self::Gemini => "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 { thread_view, .. } => {
 848                thread_view.update(cx, |thread_element, cx| {
 849                    thread_element.cancel_generation(cx)
 850                });
 851            }
 852            ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {}
 853        }
 854    }
 855
 856    fn active_message_editor(&self) -> Option<&Entity<MessageEditor>> {
 857        match &self.active_view {
 858            ActiveView::Thread { message_editor, .. } => Some(message_editor),
 859            ActiveView::ExternalAgentThread { .. }
 860            | ActiveView::TextThread { .. }
 861            | ActiveView::History
 862            | ActiveView::Configuration => None,
 863        }
 864    }
 865
 866    fn new_thread(&mut self, action: &NewThread, window: &mut Window, cx: &mut Context<Self>) {
 867        // Preserve chat box text when using creating new thread
 868        let preserved_text = self
 869            .active_message_editor()
 870            .map(|editor| editor.read(cx).get_text(cx).trim().to_string());
 871
 872        let thread = self
 873            .thread_store
 874            .update(cx, |this, cx| this.create_thread(cx));
 875
 876        let context_store = cx.new(|_cx| {
 877            ContextStore::new(
 878                self.project.downgrade(),
 879                Some(self.thread_store.downgrade()),
 880            )
 881        });
 882
 883        if let Some(other_thread_id) = action.from_thread_id.clone() {
 884            let other_thread_task = self.thread_store.update(cx, |this, cx| {
 885                this.open_thread(&other_thread_id, window, cx)
 886            });
 887
 888            cx.spawn({
 889                let context_store = context_store.clone();
 890
 891                async move |_panel, cx| {
 892                    let other_thread = other_thread_task.await?;
 893
 894                    context_store.update(cx, |this, cx| {
 895                        this.add_thread(other_thread, false, cx);
 896                    })?;
 897                    anyhow::Ok(())
 898                }
 899            })
 900            .detach_and_log_err(cx);
 901        }
 902
 903        let active_thread = cx.new(|cx| {
 904            ActiveThread::new(
 905                thread.clone(),
 906                self.thread_store.clone(),
 907                self.context_store.clone(),
 908                context_store.clone(),
 909                self.language_registry.clone(),
 910                self.workspace.clone(),
 911                window,
 912                cx,
 913            )
 914        });
 915
 916        let message_editor = cx.new(|cx| {
 917            MessageEditor::new(
 918                self.fs.clone(),
 919                self.workspace.clone(),
 920                context_store.clone(),
 921                self.prompt_store.clone(),
 922                self.thread_store.downgrade(),
 923                self.context_store.downgrade(),
 924                Some(self.history_store.downgrade()),
 925                thread.clone(),
 926                window,
 927                cx,
 928            )
 929        });
 930
 931        if let Some(text) = preserved_text {
 932            message_editor.update(cx, |editor, cx| {
 933                editor.set_text(text, window, cx);
 934            });
 935        }
 936
 937        message_editor.focus_handle(cx).focus(window);
 938
 939        let thread_view = ActiveView::thread(active_thread.clone(), message_editor, window, cx);
 940        self.set_active_view(thread_view, window, cx);
 941
 942        AgentDiff::set_active_thread(&self.workspace, thread.clone(), window, cx);
 943    }
 944
 945    fn new_prompt_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 946        let context = self
 947            .context_store
 948            .update(cx, |context_store, cx| context_store.create(cx));
 949        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
 950            .log_err()
 951            .flatten();
 952
 953        let context_editor = cx.new(|cx| {
 954            let mut editor = TextThreadEditor::for_context(
 955                context,
 956                self.fs.clone(),
 957                self.workspace.clone(),
 958                self.project.clone(),
 959                lsp_adapter_delegate,
 960                window,
 961                cx,
 962            );
 963            editor.insert_default_prompt(window, cx);
 964            editor
 965        });
 966
 967        self.set_active_view(
 968            ActiveView::prompt_editor(
 969                context_editor.clone(),
 970                self.history_store.clone(),
 971                self.language_registry.clone(),
 972                window,
 973                cx,
 974            ),
 975            window,
 976            cx,
 977        );
 978        context_editor.focus_handle(cx).focus(window);
 979    }
 980
 981    fn new_external_thread(
 982        &mut self,
 983        agent_choice: Option<crate::ExternalAgent>,
 984        restore_thread: Option<AcpThreadMetadata>,
 985        window: &mut Window,
 986        cx: &mut Context<Self>,
 987    ) {
 988        let workspace = self.workspace.clone();
 989        let project = self.project.clone();
 990        let fs = self.fs.clone();
 991
 992        const LAST_USED_EXTERNAL_AGENT_KEY: &str = "agent_panel__last_used_external_agent";
 993
 994        #[derive(Default, Serialize, Deserialize)]
 995        struct LastUsedExternalAgent {
 996            agent: crate::ExternalAgent,
 997        }
 998
 999        let thread_store = self.thread_store.clone();
1000        let text_thread_store = self.context_store.clone();
1001
1002        cx.spawn_in(window, async move |this, cx| {
1003            let server: Rc<dyn AgentServer> = match agent_choice {
1004                Some(agent) => {
1005                    cx.background_spawn(async move {
1006                        if let Some(serialized) =
1007                            serde_json::to_string(&LastUsedExternalAgent { agent }).log_err()
1008                        {
1009                            KEY_VALUE_STORE
1010                                .write_kvp(LAST_USED_EXTERNAL_AGENT_KEY.to_string(), serialized)
1011                                .await
1012                                .log_err();
1013                        }
1014                    })
1015                    .detach();
1016
1017                    agent.server(fs)
1018                }
1019                None => cx
1020                    .background_spawn(async move {
1021                        KEY_VALUE_STORE.read_kvp(LAST_USED_EXTERNAL_AGENT_KEY)
1022                    })
1023                    .await
1024                    .log_err()
1025                    .flatten()
1026                    .and_then(|value| {
1027                        serde_json::from_str::<LastUsedExternalAgent>(&value).log_err()
1028                    })
1029                    .unwrap_or_default()
1030                    .agent
1031                    .server(fs),
1032            };
1033
1034            this.update_in(cx, |this, window, cx| {
1035                let thread_view = cx.new(|cx| {
1036                    crate::acp::AcpThreadView::new(
1037                        server,
1038                        workspace.clone(),
1039                        project,
1040                        thread_store.clone(),
1041                        text_thread_store.clone(),
1042                        restore_thread,
1043                        window,
1044                        cx,
1045                    )
1046                });
1047
1048                this.set_active_view(ActiveView::ExternalAgentThread { thread_view }, window, cx);
1049            })
1050        })
1051        .detach_and_log_err(cx);
1052    }
1053
1054    fn deploy_rules_library(
1055        &mut self,
1056        action: &OpenRulesLibrary,
1057        _window: &mut Window,
1058        cx: &mut Context<Self>,
1059    ) {
1060        open_rules_library(
1061            self.language_registry.clone(),
1062            Box::new(PromptLibraryInlineAssist::new(self.workspace.clone())),
1063            Rc::new(|| {
1064                Rc::new(SlashCommandCompletionProvider::new(
1065                    Arc::new(SlashCommandWorkingSet::default()),
1066                    None,
1067                    None,
1068                ))
1069            }),
1070            action
1071                .prompt_to_select
1072                .map(|uuid| UserPromptId(uuid).into()),
1073            cx,
1074        )
1075        .detach_and_log_err(cx);
1076    }
1077
1078    fn open_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1079        if matches!(self.active_view, ActiveView::History) {
1080            if let Some(previous_view) = self.previous_view.take() {
1081                self.set_active_view(previous_view, window, cx);
1082            }
1083        } else {
1084            self.thread_store
1085                .update(cx, |thread_store, cx| thread_store.reload(cx))
1086                .detach_and_log_err(cx);
1087            self.set_active_view(ActiveView::History, window, cx);
1088        }
1089        cx.notify();
1090    }
1091
1092    pub(crate) fn open_saved_prompt_editor(
1093        &mut self,
1094        path: Arc<Path>,
1095        window: &mut Window,
1096        cx: &mut Context<Self>,
1097    ) -> Task<Result<()>> {
1098        let context = self
1099            .context_store
1100            .update(cx, |store, cx| store.open_local_context(path, cx));
1101        cx.spawn_in(window, async move |this, cx| {
1102            let context = context.await?;
1103            this.update_in(cx, |this, window, cx| {
1104                this.open_prompt_editor(context, window, cx);
1105            })
1106        })
1107    }
1108
1109    pub(crate) fn open_prompt_editor(
1110        &mut self,
1111        context: Entity<AssistantContext>,
1112        window: &mut Window,
1113        cx: &mut Context<Self>,
1114    ) {
1115        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project.clone(), cx)
1116            .log_err()
1117            .flatten();
1118        let editor = cx.new(|cx| {
1119            TextThreadEditor::for_context(
1120                context,
1121                self.fs.clone(),
1122                self.workspace.clone(),
1123                self.project.clone(),
1124                lsp_adapter_delegate,
1125                window,
1126                cx,
1127            )
1128        });
1129        self.set_active_view(
1130            ActiveView::prompt_editor(
1131                editor.clone(),
1132                self.history_store.clone(),
1133                self.language_registry.clone(),
1134                window,
1135                cx,
1136            ),
1137            window,
1138            cx,
1139        );
1140    }
1141
1142    pub(crate) fn open_thread_by_id(
1143        &mut self,
1144        thread_id: &ThreadId,
1145        window: &mut Window,
1146        cx: &mut Context<Self>,
1147    ) -> Task<Result<()>> {
1148        let open_thread_task = self
1149            .thread_store
1150            .update(cx, |this, cx| this.open_thread(thread_id, window, cx));
1151        cx.spawn_in(window, async move |this, cx| {
1152            let thread = open_thread_task.await?;
1153            this.update_in(cx, |this, window, cx| {
1154                this.open_thread(thread, window, cx);
1155                anyhow::Ok(())
1156            })??;
1157            Ok(())
1158        })
1159    }
1160
1161    pub(crate) fn open_thread(
1162        &mut self,
1163        thread: Entity<Thread>,
1164        window: &mut Window,
1165        cx: &mut Context<Self>,
1166    ) {
1167        let context_store = cx.new(|_cx| {
1168            ContextStore::new(
1169                self.project.downgrade(),
1170                Some(self.thread_store.downgrade()),
1171            )
1172        });
1173
1174        let active_thread = cx.new(|cx| {
1175            ActiveThread::new(
1176                thread.clone(),
1177                self.thread_store.clone(),
1178                self.context_store.clone(),
1179                context_store.clone(),
1180                self.language_registry.clone(),
1181                self.workspace.clone(),
1182                window,
1183                cx,
1184            )
1185        });
1186
1187        let message_editor = cx.new(|cx| {
1188            MessageEditor::new(
1189                self.fs.clone(),
1190                self.workspace.clone(),
1191                context_store,
1192                self.prompt_store.clone(),
1193                self.thread_store.downgrade(),
1194                self.context_store.downgrade(),
1195                Some(self.history_store.downgrade()),
1196                thread.clone(),
1197                window,
1198                cx,
1199            )
1200        });
1201        message_editor.focus_handle(cx).focus(window);
1202
1203        let thread_view = ActiveView::thread(active_thread.clone(), message_editor, window, cx);
1204        self.set_active_view(thread_view, window, cx);
1205        AgentDiff::set_active_thread(&self.workspace, thread.clone(), window, cx);
1206    }
1207
1208    pub fn go_back(&mut self, _: &workspace::GoBack, window: &mut Window, cx: &mut Context<Self>) {
1209        match self.active_view {
1210            ActiveView::Configuration | ActiveView::History => {
1211                if let Some(previous_view) = self.previous_view.take() {
1212                    self.active_view = previous_view;
1213
1214                    match &self.active_view {
1215                        ActiveView::Thread { message_editor, .. } => {
1216                            message_editor.focus_handle(cx).focus(window);
1217                        }
1218                        ActiveView::ExternalAgentThread { thread_view } => {
1219                            thread_view.focus_handle(cx).focus(window);
1220                        }
1221                        ActiveView::TextThread { context_editor, .. } => {
1222                            context_editor.focus_handle(cx).focus(window);
1223                        }
1224                        ActiveView::History | ActiveView::Configuration => {}
1225                    }
1226                }
1227                cx.notify();
1228            }
1229            _ => {}
1230        }
1231    }
1232
1233    pub fn toggle_navigation_menu(
1234        &mut self,
1235        _: &ToggleNavigationMenu,
1236        window: &mut Window,
1237        cx: &mut Context<Self>,
1238    ) {
1239        self.assistant_navigation_menu_handle.toggle(window, cx);
1240    }
1241
1242    pub fn toggle_options_menu(
1243        &mut self,
1244        _: &ToggleOptionsMenu,
1245        window: &mut Window,
1246        cx: &mut Context<Self>,
1247    ) {
1248        self.agent_panel_menu_handle.toggle(window, cx);
1249    }
1250
1251    pub fn toggle_new_thread_menu(
1252        &mut self,
1253        _: &ToggleNewThreadMenu,
1254        window: &mut Window,
1255        cx: &mut Context<Self>,
1256    ) {
1257        self.new_thread_menu_handle.toggle(window, cx);
1258    }
1259
1260    pub fn increase_font_size(
1261        &mut self,
1262        action: &IncreaseBufferFontSize,
1263        _: &mut Window,
1264        cx: &mut Context<Self>,
1265    ) {
1266        self.handle_font_size_action(action.persist, px(1.0), cx);
1267    }
1268
1269    pub fn decrease_font_size(
1270        &mut self,
1271        action: &DecreaseBufferFontSize,
1272        _: &mut Window,
1273        cx: &mut Context<Self>,
1274    ) {
1275        self.handle_font_size_action(action.persist, px(-1.0), cx);
1276    }
1277
1278    fn handle_font_size_action(&mut self, persist: bool, delta: Pixels, cx: &mut Context<Self>) {
1279        match self.active_view.which_font_size_used() {
1280            WhichFontSize::AgentFont => {
1281                if persist {
1282                    update_settings_file::<ThemeSettings>(
1283                        self.fs.clone(),
1284                        cx,
1285                        move |settings, cx| {
1286                            let agent_font_size =
1287                                ThemeSettings::get_global(cx).agent_font_size(cx) + delta;
1288                            let _ = settings
1289                                .agent_font_size
1290                                .insert(theme::clamp_font_size(agent_font_size).0);
1291                        },
1292                    );
1293                } else {
1294                    theme::adjust_agent_font_size(cx, |size| {
1295                        *size += delta;
1296                    });
1297                }
1298            }
1299            WhichFontSize::BufferFont => {
1300                // Prompt editor uses the buffer font size, so allow the action to propagate to the
1301                // default handler that changes that font size.
1302                cx.propagate();
1303            }
1304            WhichFontSize::None => {}
1305        }
1306    }
1307
1308    pub fn reset_font_size(
1309        &mut self,
1310        action: &ResetBufferFontSize,
1311        _: &mut Window,
1312        cx: &mut Context<Self>,
1313    ) {
1314        if action.persist {
1315            update_settings_file::<ThemeSettings>(self.fs.clone(), cx, move |settings, _| {
1316                settings.agent_font_size = None;
1317            });
1318        } else {
1319            theme::reset_agent_font_size(cx);
1320        }
1321    }
1322
1323    pub fn toggle_zoom(&mut self, _: &ToggleZoom, window: &mut Window, cx: &mut Context<Self>) {
1324        if self.zoomed {
1325            cx.emit(PanelEvent::ZoomOut);
1326        } else {
1327            if !self.focus_handle(cx).contains_focused(window, cx) {
1328                cx.focus_self(window);
1329            }
1330            cx.emit(PanelEvent::ZoomIn);
1331        }
1332    }
1333
1334    pub fn open_agent_diff(
1335        &mut self,
1336        _: &OpenAgentDiff,
1337        window: &mut Window,
1338        cx: &mut Context<Self>,
1339    ) {
1340        match &self.active_view {
1341            ActiveView::Thread { thread, .. } => {
1342                let thread = thread.read(cx).thread().clone();
1343                self.workspace
1344                    .update(cx, |workspace, cx| {
1345                        AgentDiffPane::deploy_in_workspace(
1346                            AgentDiffThread::Native(thread),
1347                            workspace,
1348                            window,
1349                            cx,
1350                        )
1351                    })
1352                    .log_err();
1353            }
1354            ActiveView::ExternalAgentThread { .. }
1355            | ActiveView::TextThread { .. }
1356            | ActiveView::History
1357            | ActiveView::Configuration => {}
1358        }
1359    }
1360
1361    pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1362        let context_server_store = self.project.read(cx).context_server_store();
1363        let tools = self.thread_store.read(cx).tools();
1364        let fs = self.fs.clone();
1365
1366        self.set_active_view(ActiveView::Configuration, window, cx);
1367        self.configuration = Some(cx.new(|cx| {
1368            AgentConfiguration::new(
1369                fs,
1370                context_server_store,
1371                tools,
1372                self.language_registry.clone(),
1373                self.workspace.clone(),
1374                window,
1375                cx,
1376            )
1377        }));
1378
1379        if let Some(configuration) = self.configuration.as_ref() {
1380            self.configuration_subscription = Some(cx.subscribe_in(
1381                configuration,
1382                window,
1383                Self::handle_agent_configuration_event,
1384            ));
1385
1386            configuration.focus_handle(cx).focus(window);
1387        }
1388    }
1389
1390    pub(crate) fn open_active_thread_as_markdown(
1391        &mut self,
1392        _: &OpenActiveThreadAsMarkdown,
1393        window: &mut Window,
1394        cx: &mut Context<Self>,
1395    ) {
1396        let Some(workspace) = self.workspace.upgrade() else {
1397            return;
1398        };
1399
1400        match &self.active_view {
1401            ActiveView::Thread { thread, .. } => {
1402                active_thread::open_active_thread_as_markdown(
1403                    thread.read(cx).thread().clone(),
1404                    workspace,
1405                    window,
1406                    cx,
1407                )
1408                .detach_and_log_err(cx);
1409            }
1410            ActiveView::ExternalAgentThread { thread_view } => {
1411                thread_view
1412                    .update(cx, |thread_view, cx| {
1413                        thread_view.open_thread_as_markdown(workspace, window, cx)
1414                    })
1415                    .detach_and_log_err(cx);
1416            }
1417            ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {}
1418        }
1419    }
1420
1421    fn handle_agent_configuration_event(
1422        &mut self,
1423        _entity: &Entity<AgentConfiguration>,
1424        event: &AssistantConfigurationEvent,
1425        window: &mut Window,
1426        cx: &mut Context<Self>,
1427    ) {
1428        match event {
1429            AssistantConfigurationEvent::NewThread(provider) => {
1430                if LanguageModelRegistry::read_global(cx)
1431                    .default_model()
1432                    .map_or(true, |model| model.provider.id() != provider.id())
1433                {
1434                    if let Some(model) = provider.default_model(cx) {
1435                        update_settings_file::<AgentSettings>(
1436                            self.fs.clone(),
1437                            cx,
1438                            move |settings, _| settings.set_model(model),
1439                        );
1440                    }
1441                }
1442
1443                self.new_thread(&NewThread::default(), window, cx);
1444                if let Some((thread, model)) =
1445                    self.active_thread(cx).zip(provider.default_model(cx))
1446                {
1447                    thread.update(cx, |thread, cx| {
1448                        thread.set_configured_model(
1449                            Some(ConfiguredModel {
1450                                provider: provider.clone(),
1451                                model,
1452                            }),
1453                            cx,
1454                        );
1455                    });
1456                }
1457            }
1458        }
1459    }
1460
1461    pub(crate) fn active_thread(&self, cx: &App) -> Option<Entity<Thread>> {
1462        match &self.active_view {
1463            ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
1464            _ => None,
1465        }
1466    }
1467
1468    pub(crate) fn delete_thread(
1469        &mut self,
1470        thread_id: &ThreadId,
1471        cx: &mut Context<Self>,
1472    ) -> Task<Result<()>> {
1473        self.thread_store
1474            .update(cx, |this, cx| this.delete_thread(thread_id, cx))
1475    }
1476
1477    fn continue_conversation(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1478        let ActiveView::Thread { thread, .. } = &self.active_view else {
1479            return;
1480        };
1481
1482        let thread_state = thread.read(cx).thread().read(cx);
1483        if !thread_state.tool_use_limit_reached() {
1484            return;
1485        }
1486
1487        let model = thread_state.configured_model().map(|cm| cm.model.clone());
1488        if let Some(model) = model {
1489            thread.update(cx, |active_thread, cx| {
1490                active_thread.thread().update(cx, |thread, cx| {
1491                    thread.insert_invisible_continue_message(cx);
1492                    thread.advance_prompt_id();
1493                    thread.send_to_model(
1494                        model,
1495                        CompletionIntent::UserPrompt,
1496                        Some(window.window_handle()),
1497                        cx,
1498                    );
1499                });
1500            });
1501        } else {
1502            log::warn!("No configured model available for continuation");
1503        }
1504    }
1505
1506    fn toggle_burn_mode(
1507        &mut self,
1508        _: &ToggleBurnMode,
1509        _window: &mut Window,
1510        cx: &mut Context<Self>,
1511    ) {
1512        let ActiveView::Thread { thread, .. } = &self.active_view else {
1513            return;
1514        };
1515
1516        thread.update(cx, |active_thread, cx| {
1517            active_thread.thread().update(cx, |thread, _cx| {
1518                let current_mode = thread.completion_mode();
1519
1520                thread.set_completion_mode(match current_mode {
1521                    CompletionMode::Burn => CompletionMode::Normal,
1522                    CompletionMode::Normal => CompletionMode::Burn,
1523                });
1524            });
1525        });
1526    }
1527
1528    pub(crate) fn active_context_editor(&self) -> Option<Entity<TextThreadEditor>> {
1529        match &self.active_view {
1530            ActiveView::TextThread { context_editor, .. } => Some(context_editor.clone()),
1531            _ => None,
1532        }
1533    }
1534
1535    pub(crate) fn delete_context(
1536        &mut self,
1537        path: Arc<Path>,
1538        cx: &mut Context<Self>,
1539    ) -> Task<Result<()>> {
1540        self.context_store
1541            .update(cx, |this, cx| this.delete_local_context(path, cx))
1542    }
1543
1544    fn set_active_view(
1545        &mut self,
1546        new_view: ActiveView,
1547        window: &mut Window,
1548        cx: &mut Context<Self>,
1549    ) {
1550        let current_is_history = matches!(self.active_view, ActiveView::History);
1551        let new_is_history = matches!(new_view, ActiveView::History);
1552
1553        let current_is_config = matches!(self.active_view, ActiveView::Configuration);
1554        let new_is_config = matches!(new_view, ActiveView::Configuration);
1555
1556        let current_is_special = current_is_history || current_is_config;
1557        let new_is_special = new_is_history || new_is_config;
1558
1559        match &self.active_view {
1560            ActiveView::Thread { thread, .. } => {
1561                let thread = thread.read(cx);
1562                if thread.is_empty() {
1563                    let id = thread.thread().read(cx).id().clone();
1564                    self.history_store.update(cx, |store, cx| {
1565                        store.remove_recently_opened_thread(id, cx);
1566                    });
1567                }
1568            }
1569            _ => {}
1570        }
1571
1572        match &new_view {
1573            ActiveView::Thread { thread, .. } => self.history_store.update(cx, |store, cx| {
1574                let id = thread.read(cx).thread().read(cx).id().clone();
1575                store.push_recently_opened_entry(HistoryEntryId::Thread(id), cx);
1576            }),
1577            ActiveView::TextThread { context_editor, .. } => {
1578                self.history_store.update(cx, |store, cx| {
1579                    if let Some(path) = context_editor.read(cx).context().read(cx).path() {
1580                        store.push_recently_opened_entry(HistoryEntryId::Context(path.clone()), cx)
1581                    }
1582                })
1583            }
1584            ActiveView::ExternalAgentThread { .. } => {}
1585            ActiveView::History | ActiveView::Configuration => {}
1586        }
1587
1588        if current_is_special && !new_is_special {
1589            self.active_view = new_view;
1590        } else if !current_is_special && new_is_special {
1591            self.previous_view = Some(std::mem::replace(&mut self.active_view, new_view));
1592        } else {
1593            if !new_is_special {
1594                self.previous_view = None;
1595            }
1596            self.active_view = new_view;
1597        }
1598
1599        self.focus_handle(cx).focus(window);
1600    }
1601
1602    fn populate_recently_opened_menu_section(
1603        mut menu: ContextMenu,
1604        panel: Entity<Self>,
1605        cx: &mut Context<ContextMenu>,
1606    ) -> ContextMenu {
1607        let entries = panel
1608            .read(cx)
1609            .history_store
1610            .read(cx)
1611            .recently_opened_entries(cx);
1612
1613        if entries.is_empty() {
1614            return menu;
1615        }
1616
1617        menu = menu.header("Recently Opened");
1618
1619        for entry in entries {
1620            let title = entry.title().clone();
1621            let id = entry.id();
1622
1623            menu = menu.entry_with_end_slot_on_hover(
1624                title,
1625                None,
1626                {
1627                    let panel = panel.downgrade();
1628                    let id = id.clone();
1629                    move |window, cx| {
1630                        let id = id.clone();
1631                        panel
1632                            .update(cx, move |this, cx| match id {
1633                                HistoryEntryId::Thread(id) => this
1634                                    .open_thread_by_id(&id, window, cx)
1635                                    .detach_and_log_err(cx),
1636                                HistoryEntryId::Context(path) => this
1637                                    .open_saved_prompt_editor(path.clone(), window, cx)
1638                                    .detach_and_log_err(cx),
1639                            })
1640                            .ok();
1641                    }
1642                },
1643                IconName::Close,
1644                "Close Entry".into(),
1645                {
1646                    let panel = panel.downgrade();
1647                    let id = id.clone();
1648                    move |_window, cx| {
1649                        panel
1650                            .update(cx, |this, cx| {
1651                                this.history_store.update(cx, |history_store, cx| {
1652                                    history_store.remove_recently_opened_entry(&id, cx);
1653                                });
1654                            })
1655                            .ok();
1656                    }
1657                },
1658            );
1659        }
1660
1661        menu = menu.separator();
1662
1663        menu
1664    }
1665
1666    pub fn set_selected_agent(&mut self, agent: AgentType, cx: &mut Context<Self>) {
1667        if self.selected_agent != agent {
1668            self.selected_agent = agent;
1669            self.serialize(cx);
1670        }
1671    }
1672
1673    pub fn selected_agent(&self) -> AgentType {
1674        self.selected_agent
1675    }
1676}
1677
1678impl Focusable for AgentPanel {
1679    fn focus_handle(&self, cx: &App) -> FocusHandle {
1680        match &self.active_view {
1681            ActiveView::Thread { message_editor, .. } => message_editor.focus_handle(cx),
1682            ActiveView::ExternalAgentThread { thread_view, .. } => thread_view.focus_handle(cx),
1683            ActiveView::History => {
1684                if cx.has_flag::<feature_flags::AcpFeatureFlag>() {
1685                    self.acp_history.focus_handle(cx)
1686                } else {
1687                    self.history.focus_handle(cx)
1688                }
1689            }
1690
1691            ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx),
1692            ActiveView::Configuration => {
1693                if let Some(configuration) = self.configuration.as_ref() {
1694                    configuration.focus_handle(cx)
1695                } else {
1696                    cx.focus_handle()
1697                }
1698            }
1699        }
1700    }
1701}
1702
1703fn agent_panel_dock_position(cx: &App) -> DockPosition {
1704    match AgentSettings::get_global(cx).dock {
1705        AgentDockPosition::Left => DockPosition::Left,
1706        AgentDockPosition::Bottom => DockPosition::Bottom,
1707        AgentDockPosition::Right => DockPosition::Right,
1708    }
1709}
1710
1711impl EventEmitter<PanelEvent> for AgentPanel {}
1712
1713impl Panel for AgentPanel {
1714    fn persistent_name() -> &'static str {
1715        "AgentPanel"
1716    }
1717
1718    fn position(&self, _window: &Window, cx: &App) -> DockPosition {
1719        agent_panel_dock_position(cx)
1720    }
1721
1722    fn position_is_valid(&self, position: DockPosition) -> bool {
1723        position != DockPosition::Bottom
1724    }
1725
1726    fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
1727        settings::update_settings_file::<AgentSettings>(self.fs.clone(), cx, move |settings, _| {
1728            let dock = match position {
1729                DockPosition::Left => AgentDockPosition::Left,
1730                DockPosition::Bottom => AgentDockPosition::Bottom,
1731                DockPosition::Right => AgentDockPosition::Right,
1732            };
1733            settings.set_dock(dock);
1734        });
1735    }
1736
1737    fn size(&self, window: &Window, cx: &App) -> Pixels {
1738        let settings = AgentSettings::get_global(cx);
1739        match self.position(window, cx) {
1740            DockPosition::Left | DockPosition::Right => {
1741                self.width.unwrap_or(settings.default_width)
1742            }
1743            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
1744        }
1745    }
1746
1747    fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
1748        match self.position(window, cx) {
1749            DockPosition::Left | DockPosition::Right => self.width = size,
1750            DockPosition::Bottom => self.height = size,
1751        }
1752        self.serialize(cx);
1753        cx.notify();
1754    }
1755
1756    fn set_active(&mut self, _active: bool, _window: &mut Window, _cx: &mut Context<Self>) {}
1757
1758    fn remote_id() -> Option<proto::PanelId> {
1759        Some(proto::PanelId::AssistantPanel)
1760    }
1761
1762    fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
1763        (self.enabled(cx) && AgentSettings::get_global(cx).button).then_some(IconName::ZedAssistant)
1764    }
1765
1766    fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
1767        Some("Agent Panel")
1768    }
1769
1770    fn toggle_action(&self) -> Box<dyn Action> {
1771        Box::new(ToggleFocus)
1772    }
1773
1774    fn activation_priority(&self) -> u32 {
1775        3
1776    }
1777
1778    fn enabled(&self, cx: &App) -> bool {
1779        DisableAiSettings::get_global(cx).disable_ai.not() && AgentSettings::get_global(cx).enabled
1780    }
1781
1782    fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
1783        self.zoomed
1784    }
1785
1786    fn set_zoomed(&mut self, zoomed: bool, _window: &mut Window, cx: &mut Context<Self>) {
1787        self.zoomed = zoomed;
1788        cx.notify();
1789    }
1790}
1791
1792impl AgentPanel {
1793    fn render_title_view(&self, _window: &mut Window, cx: &Context<Self>) -> AnyElement {
1794        const LOADING_SUMMARY_PLACEHOLDER: &str = "Loading Summary…";
1795
1796        let content = match &self.active_view {
1797            ActiveView::Thread {
1798                thread: active_thread,
1799                change_title_editor,
1800                ..
1801            } => {
1802                let state = {
1803                    let active_thread = active_thread.read(cx);
1804                    if active_thread.is_empty() {
1805                        &ThreadSummary::Pending
1806                    } else {
1807                        active_thread.summary(cx)
1808                    }
1809                };
1810
1811                match state {
1812                    ThreadSummary::Pending => Label::new(ThreadSummary::DEFAULT.clone())
1813                        .truncate()
1814                        .into_any_element(),
1815                    ThreadSummary::Generating => Label::new(LOADING_SUMMARY_PLACEHOLDER)
1816                        .truncate()
1817                        .into_any_element(),
1818                    ThreadSummary::Ready(_) => div()
1819                        .w_full()
1820                        .child(change_title_editor.clone())
1821                        .into_any_element(),
1822                    ThreadSummary::Error => h_flex()
1823                        .w_full()
1824                        .child(change_title_editor.clone())
1825                        .child(
1826                            ui::IconButton::new("retry-summary-generation", IconName::RotateCcw)
1827                                .on_click({
1828                                    let active_thread = active_thread.clone();
1829                                    move |_, _window, cx| {
1830                                        active_thread.update(cx, |thread, cx| {
1831                                            thread.regenerate_summary(cx);
1832                                        });
1833                                    }
1834                                })
1835                                .tooltip(move |_window, cx| {
1836                                    cx.new(|_| {
1837                                        Tooltip::new("Failed to generate title")
1838                                            .meta("Click to try again")
1839                                    })
1840                                    .into()
1841                                }),
1842                        )
1843                        .into_any_element(),
1844                }
1845            }
1846            ActiveView::ExternalAgentThread { thread_view } => {
1847                Label::new(thread_view.read(cx).title(cx))
1848                    .truncate()
1849                    .into_any_element()
1850            }
1851            ActiveView::TextThread {
1852                title_editor,
1853                context_editor,
1854                ..
1855            } => {
1856                let summary = context_editor.read(cx).context().read(cx).summary();
1857
1858                match summary {
1859                    ContextSummary::Pending => Label::new(ContextSummary::DEFAULT)
1860                        .truncate()
1861                        .into_any_element(),
1862                    ContextSummary::Content(summary) => {
1863                        if summary.done {
1864                            div()
1865                                .w_full()
1866                                .child(title_editor.clone())
1867                                .into_any_element()
1868                        } else {
1869                            Label::new(LOADING_SUMMARY_PLACEHOLDER)
1870                                .truncate()
1871                                .into_any_element()
1872                        }
1873                    }
1874                    ContextSummary::Error => h_flex()
1875                        .w_full()
1876                        .child(title_editor.clone())
1877                        .child(
1878                            ui::IconButton::new("retry-summary-generation", IconName::RotateCcw)
1879                                .on_click({
1880                                    let context_editor = context_editor.clone();
1881                                    move |_, _window, cx| {
1882                                        context_editor.update(cx, |context_editor, cx| {
1883                                            context_editor.regenerate_summary(cx);
1884                                        });
1885                                    }
1886                                })
1887                                .tooltip(move |_window, cx| {
1888                                    cx.new(|_| {
1889                                        Tooltip::new("Failed to generate title")
1890                                            .meta("Click to try again")
1891                                    })
1892                                    .into()
1893                                }),
1894                        )
1895                        .into_any_element(),
1896                }
1897            }
1898            ActiveView::History => Label::new("History").truncate().into_any_element(),
1899            ActiveView::Configuration => Label::new("Settings").truncate().into_any_element(),
1900        };
1901
1902        h_flex()
1903            .key_context("TitleEditor")
1904            .id("TitleEditor")
1905            .flex_grow()
1906            .w_full()
1907            .max_w_full()
1908            .overflow_x_scroll()
1909            .child(content)
1910            .into_any()
1911    }
1912
1913    fn render_panel_options_menu(
1914        &self,
1915        window: &mut Window,
1916        cx: &mut Context<Self>,
1917    ) -> impl IntoElement {
1918        let user_store = self.user_store.read(cx);
1919        let usage = user_store.model_request_usage();
1920        let account_url = zed_urls::account_url(cx);
1921
1922        let focus_handle = self.focus_handle(cx);
1923
1924        let full_screen_label = if self.is_zoomed(window, cx) {
1925            "Disable Full Screen"
1926        } else {
1927            "Enable Full Screen"
1928        };
1929
1930        PopoverMenu::new("agent-options-menu")
1931            .trigger_with_tooltip(
1932                IconButton::new("agent-options-menu", IconName::Ellipsis)
1933                    .icon_size(IconSize::Small),
1934                {
1935                    let focus_handle = focus_handle.clone();
1936                    move |window, cx| {
1937                        Tooltip::for_action_in(
1938                            "Toggle Agent Menu",
1939                            &ToggleOptionsMenu,
1940                            &focus_handle,
1941                            window,
1942                            cx,
1943                        )
1944                    }
1945                },
1946            )
1947            .anchor(Corner::TopRight)
1948            .with_handle(self.agent_panel_menu_handle.clone())
1949            .menu({
1950                let focus_handle = focus_handle.clone();
1951                move |window, cx| {
1952                    Some(ContextMenu::build(window, cx, |mut menu, _window, _| {
1953                        menu = menu.context(focus_handle.clone());
1954                        if let Some(usage) = usage {
1955                            menu = menu
1956                                .header_with_link("Prompt Usage", "Manage", account_url.clone())
1957                                .custom_entry(
1958                                    move |_window, cx| {
1959                                        let used_percentage = match usage.limit {
1960                                            UsageLimit::Limited(limit) => {
1961                                                Some((usage.amount as f32 / limit as f32) * 100.)
1962                                            }
1963                                            UsageLimit::Unlimited => None,
1964                                        };
1965
1966                                        h_flex()
1967                                            .flex_1()
1968                                            .gap_1p5()
1969                                            .children(used_percentage.map(|percent| {
1970                                                ProgressBar::new("usage", percent, 100., cx)
1971                                            }))
1972                                            .child(
1973                                                Label::new(match usage.limit {
1974                                                    UsageLimit::Limited(limit) => {
1975                                                        format!("{} / {limit}", usage.amount)
1976                                                    }
1977                                                    UsageLimit::Unlimited => {
1978                                                        format!("{} / ∞", usage.amount)
1979                                                    }
1980                                                })
1981                                                .size(LabelSize::Small)
1982                                                .color(Color::Muted),
1983                                            )
1984                                            .into_any_element()
1985                                    },
1986                                    move |_, cx| cx.open_url(&zed_urls::account_url(cx)),
1987                                )
1988                                .separator()
1989                        }
1990
1991                        menu = menu
1992                            .header("MCP Servers")
1993                            .action(
1994                                "View Server Extensions",
1995                                Box::new(zed_actions::Extensions {
1996                                    category_filter: Some(
1997                                        zed_actions::ExtensionCategoryFilter::ContextServers,
1998                                    ),
1999                                    id: None,
2000                                }),
2001                            )
2002                            .action("Add Custom Server…", Box::new(AddContextServer))
2003                            .separator();
2004
2005                        menu = menu
2006                            .action("Rules…", Box::new(OpenRulesLibrary::default()))
2007                            .action("Settings", Box::new(OpenSettings))
2008                            .separator()
2009                            .action(full_screen_label, Box::new(ToggleZoom));
2010                        menu
2011                    }))
2012                }
2013            })
2014    }
2015
2016    fn render_recent_entries_menu(
2017        &self,
2018        icon: IconName,
2019        cx: &mut Context<Self>,
2020    ) -> impl IntoElement {
2021        let focus_handle = self.focus_handle(cx);
2022
2023        PopoverMenu::new("agent-nav-menu")
2024            .trigger_with_tooltip(
2025                IconButton::new("agent-nav-menu", icon).icon_size(IconSize::Small),
2026                {
2027                    let focus_handle = focus_handle.clone();
2028                    move |window, cx| {
2029                        Tooltip::for_action_in(
2030                            "Toggle Panel Menu",
2031                            &ToggleNavigationMenu,
2032                            &focus_handle,
2033                            window,
2034                            cx,
2035                        )
2036                    }
2037                },
2038            )
2039            .anchor(Corner::TopLeft)
2040            .with_handle(self.assistant_navigation_menu_handle.clone())
2041            .menu({
2042                let menu = self.assistant_navigation_menu.clone();
2043                move |window, cx| {
2044                    if let Some(menu) = menu.as_ref() {
2045                        menu.update(cx, |_, cx| {
2046                            cx.defer_in(window, |menu, window, cx| {
2047                                menu.rebuild(window, cx);
2048                            });
2049                        })
2050                    }
2051                    menu.clone()
2052                }
2053            })
2054    }
2055
2056    fn render_toolbar_back_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
2057        let focus_handle = self.focus_handle(cx);
2058
2059        IconButton::new("go-back", IconName::ArrowLeft)
2060            .icon_size(IconSize::Small)
2061            .on_click(cx.listener(|this, _, window, cx| {
2062                this.go_back(&workspace::GoBack, window, cx);
2063            }))
2064            .tooltip({
2065                let focus_handle = focus_handle.clone();
2066
2067                move |window, cx| {
2068                    Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, window, cx)
2069                }
2070            })
2071    }
2072
2073    fn render_toolbar_old(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2074        let focus_handle = self.focus_handle(cx);
2075
2076        let active_thread = match &self.active_view {
2077            ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
2078            ActiveView::ExternalAgentThread { .. }
2079            | ActiveView::TextThread { .. }
2080            | ActiveView::History
2081            | ActiveView::Configuration => None,
2082        };
2083
2084        let new_thread_menu = PopoverMenu::new("new_thread_menu")
2085            .trigger_with_tooltip(
2086                IconButton::new("new_thread_menu_btn", IconName::Plus).icon_size(IconSize::Small),
2087                Tooltip::text("New Thread…"),
2088            )
2089            .anchor(Corner::TopRight)
2090            .with_handle(self.new_thread_menu_handle.clone())
2091            .menu({
2092                let focus_handle = focus_handle.clone();
2093                move |window, cx| {
2094                    let active_thread = active_thread.clone();
2095                    Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
2096                        menu = menu
2097                            .context(focus_handle.clone())
2098                            .when_some(active_thread, |this, active_thread| {
2099                                let thread = active_thread.read(cx);
2100
2101                                if !thread.is_empty() {
2102                                    let thread_id = thread.id().clone();
2103                                    this.item(
2104                                        ContextMenuEntry::new("New From Summary")
2105                                            .icon(IconName::ThreadFromSummary)
2106                                            .icon_color(Color::Muted)
2107                                            .handler(move |window, cx| {
2108                                                window.dispatch_action(
2109                                                    Box::new(NewThread {
2110                                                        from_thread_id: Some(thread_id.clone()),
2111                                                    }),
2112                                                    cx,
2113                                                );
2114                                            }),
2115                                    )
2116                                } else {
2117                                    this
2118                                }
2119                            })
2120                            .item(
2121                                ContextMenuEntry::new("New Thread")
2122                                    .icon(IconName::Thread)
2123                                    .icon_color(Color::Muted)
2124                                    .action(NewThread::default().boxed_clone())
2125                                    .handler(move |window, cx| {
2126                                        window.dispatch_action(
2127                                            NewThread::default().boxed_clone(),
2128                                            cx,
2129                                        );
2130                                    }),
2131                            )
2132                            .item(
2133                                ContextMenuEntry::new("New Text Thread")
2134                                    .icon(IconName::TextThread)
2135                                    .icon_color(Color::Muted)
2136                                    .action(NewTextThread.boxed_clone())
2137                                    .handler(move |window, cx| {
2138                                        window.dispatch_action(NewTextThread.boxed_clone(), cx);
2139                                    }),
2140                            );
2141                        menu
2142                    }))
2143                }
2144            });
2145
2146        h_flex()
2147            .id("assistant-toolbar")
2148            .h(Tab::container_height(cx))
2149            .max_w_full()
2150            .flex_none()
2151            .justify_between()
2152            .gap_2()
2153            .bg(cx.theme().colors().tab_bar_background)
2154            .border_b_1()
2155            .border_color(cx.theme().colors().border)
2156            .child(
2157                h_flex()
2158                    .size_full()
2159                    .pl_1()
2160                    .gap_1()
2161                    .child(match &self.active_view {
2162                        ActiveView::History | ActiveView::Configuration => div()
2163                            .pl(DynamicSpacing::Base04.rems(cx))
2164                            .child(self.render_toolbar_back_button(cx))
2165                            .into_any_element(),
2166                        _ => self
2167                            .render_recent_entries_menu(IconName::MenuAlt, cx)
2168                            .into_any_element(),
2169                    })
2170                    .child(self.render_title_view(window, cx)),
2171            )
2172            .child(
2173                h_flex()
2174                    .h_full()
2175                    .gap_2()
2176                    .children(self.render_token_count(cx))
2177                    .child(
2178                        h_flex()
2179                            .h_full()
2180                            .gap(DynamicSpacing::Base02.rems(cx))
2181                            .px(DynamicSpacing::Base08.rems(cx))
2182                            .border_l_1()
2183                            .border_color(cx.theme().colors().border)
2184                            .child(new_thread_menu)
2185                            .child(self.render_panel_options_menu(window, cx)),
2186                    ),
2187            )
2188    }
2189
2190    fn render_toolbar_new(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2191        let focus_handle = self.focus_handle(cx);
2192
2193        let active_thread = match &self.active_view {
2194            ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
2195            ActiveView::ExternalAgentThread { .. }
2196            | ActiveView::TextThread { .. }
2197            | ActiveView::History
2198            | ActiveView::Configuration => None,
2199        };
2200
2201        let new_thread_menu = PopoverMenu::new("new_thread_menu")
2202            .trigger_with_tooltip(
2203                IconButton::new("new_thread_menu_btn", IconName::Plus).icon_size(IconSize::Small),
2204                {
2205                    let focus_handle = focus_handle.clone();
2206                    move |window, cx| {
2207                        Tooltip::for_action_in(
2208                            "New…",
2209                            &ToggleNewThreadMenu,
2210                            &focus_handle,
2211                            window,
2212                            cx,
2213                        )
2214                    }
2215                },
2216            )
2217            .anchor(Corner::TopLeft)
2218            .with_handle(self.new_thread_menu_handle.clone())
2219            .menu({
2220                let focus_handle = focus_handle.clone();
2221                let workspace = self.workspace.clone();
2222
2223                move |window, cx| {
2224                    let active_thread = active_thread.clone();
2225                    Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
2226                        menu = menu
2227                            .context(focus_handle.clone())
2228                            .header("Zed Agent")
2229                            .when_some(active_thread, |this, active_thread| {
2230                                let thread = active_thread.read(cx);
2231
2232                                if !thread.is_empty() {
2233                                    let thread_id = thread.id().clone();
2234                                    this.item(
2235                                        ContextMenuEntry::new("New From Summary")
2236                                            .icon(IconName::ThreadFromSummary)
2237                                            .icon_color(Color::Muted)
2238                                            .handler(move |window, cx| {
2239                                                window.dispatch_action(
2240                                                    Box::new(NewThread {
2241                                                        from_thread_id: Some(thread_id.clone()),
2242                                                    }),
2243                                                    cx,
2244                                                );
2245                                            }),
2246                                    )
2247                                } else {
2248                                    this
2249                                }
2250                            })
2251                            .item(
2252                                ContextMenuEntry::new("New Thread")
2253                                    .icon(IconName::Thread)
2254                                    .icon_color(Color::Muted)
2255                                    .action(NewThread::default().boxed_clone())
2256                                    .handler({
2257                                        let workspace = workspace.clone();
2258                                        move |window, cx| {
2259                                            if let Some(workspace) = workspace.upgrade() {
2260                                                workspace.update(cx, |workspace, cx| {
2261                                                    if let Some(panel) =
2262                                                        workspace.panel::<AgentPanel>(cx)
2263                                                    {
2264                                                        panel.update(cx, |panel, cx| {
2265                                                            panel.set_selected_agent(
2266                                                                AgentType::Zed,
2267                                                                cx,
2268                                                            );
2269                                                        });
2270                                                    }
2271                                                });
2272                                            }
2273                                            window.dispatch_action(
2274                                                NewThread::default().boxed_clone(),
2275                                                cx,
2276                                            );
2277                                        }
2278                                    }),
2279                            )
2280                            .item(
2281                                ContextMenuEntry::new("New Text Thread")
2282                                    .icon(IconName::TextThread)
2283                                    .icon_color(Color::Muted)
2284                                    .action(NewTextThread.boxed_clone())
2285                                    .handler({
2286                                        let workspace = workspace.clone();
2287                                        move |window, cx| {
2288                                            if let Some(workspace) = workspace.upgrade() {
2289                                                workspace.update(cx, |workspace, cx| {
2290                                                    if let Some(panel) =
2291                                                        workspace.panel::<AgentPanel>(cx)
2292                                                    {
2293                                                        panel.update(cx, |panel, cx| {
2294                                                            panel.set_selected_agent(
2295                                                                AgentType::TextThread,
2296                                                                cx,
2297                                                            );
2298                                                        });
2299                                                    }
2300                                                });
2301                                            }
2302                                            window.dispatch_action(NewTextThread.boxed_clone(), cx);
2303                                        }
2304                                    }),
2305                            )
2306                            .item(
2307                                ContextMenuEntry::new("New Native Agent Thread")
2308                                    .icon(IconName::ZedAssistant)
2309                                    .icon_color(Color::Muted)
2310                                    .handler({
2311                                        let workspace = workspace.clone();
2312                                        move |window, cx| {
2313                                            if let Some(workspace) = workspace.upgrade() {
2314                                                workspace.update(cx, |workspace, cx| {
2315                                                    if let Some(panel) =
2316                                                        workspace.panel::<AgentPanel>(cx)
2317                                                    {
2318                                                        panel.update(cx, |panel, cx| {
2319                                                            panel.set_selected_agent(
2320                                                                AgentType::NativeAgent,
2321                                                                cx,
2322                                                            );
2323                                                        });
2324                                                    }
2325                                                });
2326                                            }
2327                                            window.dispatch_action(
2328                                                NewExternalAgentThread {
2329                                                    agent: Some(crate::ExternalAgent::NativeAgent),
2330                                                }
2331                                                .boxed_clone(),
2332                                                cx,
2333                                            );
2334                                        }
2335                                    }),
2336                            )
2337                            .separator()
2338                            .header("External Agents")
2339                            .item(
2340                                ContextMenuEntry::new("New Gemini Thread")
2341                                    .icon(IconName::AiGemini)
2342                                    .icon_color(Color::Muted)
2343                                    .handler({
2344                                        let workspace = workspace.clone();
2345                                        move |window, cx| {
2346                                            if let Some(workspace) = workspace.upgrade() {
2347                                                workspace.update(cx, |workspace, cx| {
2348                                                    if let Some(panel) =
2349                                                        workspace.panel::<AgentPanel>(cx)
2350                                                    {
2351                                                        panel.update(cx, |panel, cx| {
2352                                                            panel.set_selected_agent(
2353                                                                AgentType::Gemini,
2354                                                                cx,
2355                                                            );
2356                                                        });
2357                                                    }
2358                                                });
2359                                            }
2360                                            window.dispatch_action(
2361                                                NewExternalAgentThread {
2362                                                    agent: Some(crate::ExternalAgent::Gemini),
2363                                                }
2364                                                .boxed_clone(),
2365                                                cx,
2366                                            );
2367                                        }
2368                                    }),
2369                            )
2370                            .item(
2371                                ContextMenuEntry::new("New Claude Code Thread")
2372                                    .icon(IconName::AiClaude)
2373                                    .icon_color(Color::Muted)
2374                                    .handler({
2375                                        let workspace = workspace.clone();
2376                                        move |window, cx| {
2377                                            if let Some(workspace) = workspace.upgrade() {
2378                                                workspace.update(cx, |workspace, cx| {
2379                                                    if let Some(panel) =
2380                                                        workspace.panel::<AgentPanel>(cx)
2381                                                    {
2382                                                        panel.update(cx, |panel, cx| {
2383                                                            panel.set_selected_agent(
2384                                                                AgentType::ClaudeCode,
2385                                                                cx,
2386                                                            );
2387                                                        });
2388                                                    }
2389                                                });
2390                                            }
2391                                            window.dispatch_action(
2392                                                NewExternalAgentThread {
2393                                                    agent: Some(crate::ExternalAgent::ClaudeCode),
2394                                                }
2395                                                .boxed_clone(),
2396                                                cx,
2397                                            );
2398                                        }
2399                                    }),
2400                            );
2401                        menu
2402                    }))
2403                }
2404            });
2405
2406        h_flex()
2407            .id("agent-panel-toolbar")
2408            .h(Tab::container_height(cx))
2409            .max_w_full()
2410            .flex_none()
2411            .justify_between()
2412            .gap_2()
2413            .bg(cx.theme().colors().tab_bar_background)
2414            .border_b_1()
2415            .border_color(cx.theme().colors().border)
2416            .child(
2417                h_flex()
2418                    .size_full()
2419                    .gap(DynamicSpacing::Base08.rems(cx))
2420                    .child(match &self.active_view {
2421                        ActiveView::History | ActiveView::Configuration => div()
2422                            .pl(DynamicSpacing::Base04.rems(cx))
2423                            .child(self.render_toolbar_back_button(cx))
2424                            .into_any_element(),
2425                        _ => h_flex()
2426                            .h_full()
2427                            .px(DynamicSpacing::Base04.rems(cx))
2428                            .border_r_1()
2429                            .border_color(cx.theme().colors().border)
2430                            .child(
2431                                h_flex()
2432                                    .px_0p5()
2433                                    .gap_1p5()
2434                                    .child(
2435                                        Icon::new(self.selected_agent.icon()).color(Color::Muted),
2436                                    )
2437                                    .child(Label::new(self.selected_agent.label())),
2438                            )
2439                            .into_any_element(),
2440                    })
2441                    .child(self.render_title_view(window, cx)),
2442            )
2443            .child(
2444                h_flex()
2445                    .h_full()
2446                    .gap_2()
2447                    .children(self.render_token_count(cx))
2448                    .child(
2449                        h_flex()
2450                            .h_full()
2451                            .gap(DynamicSpacing::Base02.rems(cx))
2452                            .pl(DynamicSpacing::Base04.rems(cx))
2453                            .pr(DynamicSpacing::Base06.rems(cx))
2454                            .border_l_1()
2455                            .border_color(cx.theme().colors().border)
2456                            .child(new_thread_menu)
2457                            .child(self.render_recent_entries_menu(IconName::HistoryRerun, cx))
2458                            .child(self.render_panel_options_menu(window, cx)),
2459                    ),
2460            )
2461    }
2462
2463    fn render_toolbar(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2464        if cx.has_flag::<feature_flags::AcpFeatureFlag>() {
2465            self.render_toolbar_new(window, cx).into_any_element()
2466        } else {
2467            self.render_toolbar_old(window, cx).into_any_element()
2468        }
2469    }
2470
2471    fn render_token_count(&self, cx: &App) -> Option<AnyElement> {
2472        match &self.active_view {
2473            ActiveView::Thread {
2474                thread,
2475                message_editor,
2476                ..
2477            } => {
2478                let active_thread = thread.read(cx);
2479                let message_editor = message_editor.read(cx);
2480
2481                let editor_empty = message_editor.is_editor_fully_empty(cx);
2482
2483                if active_thread.is_empty() && editor_empty {
2484                    return None;
2485                }
2486
2487                let thread = active_thread.thread().read(cx);
2488                let is_generating = thread.is_generating();
2489                let conversation_token_usage = thread.total_token_usage()?;
2490
2491                let (total_token_usage, is_estimating) =
2492                    if let Some((editing_message_id, unsent_tokens)) =
2493                        active_thread.editing_message_id()
2494                    {
2495                        let combined = thread
2496                            .token_usage_up_to_message(editing_message_id)
2497                            .add(unsent_tokens);
2498
2499                        (combined, unsent_tokens > 0)
2500                    } else {
2501                        let unsent_tokens =
2502                            message_editor.last_estimated_token_count().unwrap_or(0);
2503                        let combined = conversation_token_usage.add(unsent_tokens);
2504
2505                        (combined, unsent_tokens > 0)
2506                    };
2507
2508                let is_waiting_to_update_token_count =
2509                    message_editor.is_waiting_to_update_token_count();
2510
2511                if total_token_usage.total == 0 {
2512                    return None;
2513                }
2514
2515                let token_color = match total_token_usage.ratio() {
2516                    TokenUsageRatio::Normal if is_estimating => Color::Default,
2517                    TokenUsageRatio::Normal => Color::Muted,
2518                    TokenUsageRatio::Warning => Color::Warning,
2519                    TokenUsageRatio::Exceeded => Color::Error,
2520                };
2521
2522                let token_count = h_flex()
2523                    .id("token-count")
2524                    .flex_shrink_0()
2525                    .gap_0p5()
2526                    .when(!is_generating && is_estimating, |parent| {
2527                        parent
2528                            .child(
2529                                h_flex()
2530                                    .mr_1()
2531                                    .size_2p5()
2532                                    .justify_center()
2533                                    .rounded_full()
2534                                    .bg(cx.theme().colors().text.opacity(0.1))
2535                                    .child(
2536                                        div().size_1().rounded_full().bg(cx.theme().colors().text),
2537                                    ),
2538                            )
2539                            .tooltip(move |window, cx| {
2540                                Tooltip::with_meta(
2541                                    "Estimated New Token Count",
2542                                    None,
2543                                    format!(
2544                                        "Current Conversation Tokens: {}",
2545                                        humanize_token_count(conversation_token_usage.total)
2546                                    ),
2547                                    window,
2548                                    cx,
2549                                )
2550                            })
2551                    })
2552                    .child(
2553                        Label::new(humanize_token_count(total_token_usage.total))
2554                            .size(LabelSize::Small)
2555                            .color(token_color)
2556                            .map(|label| {
2557                                if is_generating || is_waiting_to_update_token_count {
2558                                    label
2559                                        .with_animation(
2560                                            "used-tokens-label",
2561                                            Animation::new(Duration::from_secs(2))
2562                                                .repeat()
2563                                                .with_easing(pulsating_between(0.6, 1.)),
2564                                            |label, delta| label.alpha(delta),
2565                                        )
2566                                        .into_any()
2567                                } else {
2568                                    label.into_any_element()
2569                                }
2570                            }),
2571                    )
2572                    .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
2573                    .child(
2574                        Label::new(humanize_token_count(total_token_usage.max))
2575                            .size(LabelSize::Small)
2576                            .color(Color::Muted),
2577                    )
2578                    .into_any();
2579
2580                Some(token_count)
2581            }
2582            ActiveView::TextThread { context_editor, .. } => {
2583                let element = render_remaining_tokens(context_editor, cx)?;
2584
2585                Some(element.into_any_element())
2586            }
2587            ActiveView::ExternalAgentThread { .. }
2588            | ActiveView::History
2589            | ActiveView::Configuration => {
2590                return None;
2591            }
2592        }
2593    }
2594
2595    fn should_render_trial_end_upsell(&self, cx: &mut Context<Self>) -> bool {
2596        if TrialEndUpsell::dismissed() {
2597            return false;
2598        }
2599
2600        match &self.active_view {
2601            ActiveView::Thread { thread, .. } => {
2602                if thread
2603                    .read(cx)
2604                    .thread()
2605                    .read(cx)
2606                    .configured_model()
2607                    .map_or(false, |model| {
2608                        model.provider.id() != language_model::ZED_CLOUD_PROVIDER_ID
2609                    })
2610                {
2611                    return false;
2612                }
2613            }
2614            ActiveView::TextThread { .. } => {
2615                if LanguageModelRegistry::global(cx)
2616                    .read(cx)
2617                    .default_model()
2618                    .map_or(false, |model| {
2619                        model.provider.id() != language_model::ZED_CLOUD_PROVIDER_ID
2620                    })
2621                {
2622                    return false;
2623                }
2624            }
2625            ActiveView::ExternalAgentThread { .. }
2626            | ActiveView::History
2627            | ActiveView::Configuration => return false,
2628        }
2629
2630        let plan = self.user_store.read(cx).plan();
2631        let has_previous_trial = self.user_store.read(cx).trial_started_at().is_some();
2632
2633        matches!(plan, Some(Plan::ZedFree)) && has_previous_trial
2634    }
2635
2636    fn should_render_onboarding(&self, cx: &mut Context<Self>) -> bool {
2637        if OnboardingUpsell::dismissed() {
2638            return false;
2639        }
2640
2641        match &self.active_view {
2642            ActiveView::Thread { .. } | ActiveView::TextThread { .. } => {
2643                let history_is_empty = self
2644                    .history_store
2645                    .update(cx, |store, cx| store.recent_entries(1, cx).is_empty());
2646
2647                let has_configured_non_zed_providers = LanguageModelRegistry::read_global(cx)
2648                    .providers()
2649                    .iter()
2650                    .any(|provider| {
2651                        provider.is_authenticated(cx)
2652                            && provider.id() != language_model::ZED_CLOUD_PROVIDER_ID
2653                    });
2654
2655                history_is_empty || !has_configured_non_zed_providers
2656            }
2657            ActiveView::ExternalAgentThread { .. }
2658            | ActiveView::History
2659            | ActiveView::Configuration => false,
2660        }
2661    }
2662
2663    fn render_onboarding(
2664        &self,
2665        _window: &mut Window,
2666        cx: &mut Context<Self>,
2667    ) -> Option<impl IntoElement> {
2668        if !self.should_render_onboarding(cx) {
2669            return None;
2670        }
2671
2672        let thread_view = matches!(&self.active_view, ActiveView::Thread { .. });
2673        let text_thread_view = matches!(&self.active_view, ActiveView::TextThread { .. });
2674
2675        Some(
2676            div()
2677                .when(thread_view, |this| {
2678                    this.size_full().bg(cx.theme().colors().panel_background)
2679                })
2680                .when(text_thread_view, |this| {
2681                    this.bg(cx.theme().colors().editor_background)
2682                })
2683                .child(self.onboarding.clone()),
2684        )
2685    }
2686
2687    fn render_backdrop(&self, cx: &mut Context<Self>) -> impl IntoElement {
2688        div()
2689            .size_full()
2690            .absolute()
2691            .inset_0()
2692            .bg(cx.theme().colors().panel_background)
2693            .opacity(0.8)
2694            .block_mouse_except_scroll()
2695    }
2696
2697    fn render_trial_end_upsell(
2698        &self,
2699        _window: &mut Window,
2700        cx: &mut Context<Self>,
2701    ) -> Option<impl IntoElement> {
2702        if !self.should_render_trial_end_upsell(cx) {
2703            return None;
2704        }
2705
2706        Some(
2707            v_flex()
2708                .absolute()
2709                .inset_0()
2710                .size_full()
2711                .bg(cx.theme().colors().panel_background)
2712                .opacity(0.85)
2713                .block_mouse_except_scroll()
2714                .child(EndTrialUpsell::new(Arc::new({
2715                    let this = cx.entity();
2716                    move |_, cx| {
2717                        this.update(cx, |_this, cx| {
2718                            TrialEndUpsell::set_dismissed(true, cx);
2719                            cx.notify();
2720                        });
2721                    }
2722                }))),
2723        )
2724    }
2725
2726    fn render_empty_state_section_header(
2727        &self,
2728        label: impl Into<SharedString>,
2729        action_slot: Option<AnyElement>,
2730        cx: &mut Context<Self>,
2731    ) -> impl IntoElement {
2732        h_flex()
2733            .mt_2()
2734            .pl_1p5()
2735            .pb_1()
2736            .w_full()
2737            .justify_between()
2738            .border_b_1()
2739            .border_color(cx.theme().colors().border_variant)
2740            .child(
2741                Label::new(label.into())
2742                    .size(LabelSize::Small)
2743                    .color(Color::Muted),
2744            )
2745            .children(action_slot)
2746    }
2747
2748    fn render_thread_empty_state(
2749        &self,
2750        window: &mut Window,
2751        cx: &mut Context<Self>,
2752    ) -> impl IntoElement {
2753        let recent_history = self
2754            .history_store
2755            .update(cx, |this, cx| this.recent_entries(6, cx));
2756
2757        let model_registry = LanguageModelRegistry::read_global(cx);
2758
2759        let configuration_error =
2760            model_registry.configuration_error(model_registry.default_model(), cx);
2761
2762        let no_error = configuration_error.is_none();
2763        let focus_handle = self.focus_handle(cx);
2764
2765        v_flex()
2766            .size_full()
2767            .bg(cx.theme().colors().panel_background)
2768            .when(recent_history.is_empty(), |this| {
2769                this.child(
2770                    v_flex()
2771                        .size_full()
2772                        .mx_auto()
2773                        .justify_center()
2774                        .items_center()
2775                        .gap_1()
2776                        .child(h_flex().child(Headline::new("Welcome to the Agent Panel")))
2777                        .when(no_error, |parent| {
2778                            parent
2779                                .child(h_flex().child(
2780                                    Label::new("Ask and build anything.").color(Color::Muted),
2781                                ))
2782                                .child(
2783                                    v_flex()
2784                                        .mt_2()
2785                                        .gap_1()
2786                                        .max_w_48()
2787                                        .child(
2788                                            Button::new("context", "Add Context")
2789                                                .label_size(LabelSize::Small)
2790                                                .icon(IconName::FileCode)
2791                                                .icon_position(IconPosition::Start)
2792                                                .icon_size(IconSize::Small)
2793                                                .icon_color(Color::Muted)
2794                                                .full_width()
2795                                                .key_binding(KeyBinding::for_action_in(
2796                                                    &ToggleContextPicker,
2797                                                    &focus_handle,
2798                                                    window,
2799                                                    cx,
2800                                                ))
2801                                                .on_click(|_event, window, cx| {
2802                                                    window.dispatch_action(
2803                                                        ToggleContextPicker.boxed_clone(),
2804                                                        cx,
2805                                                    )
2806                                                }),
2807                                        )
2808                                        .child(
2809                                            Button::new("mode", "Switch Model")
2810                                                .label_size(LabelSize::Small)
2811                                                .icon(IconName::DatabaseZap)
2812                                                .icon_position(IconPosition::Start)
2813                                                .icon_size(IconSize::Small)
2814                                                .icon_color(Color::Muted)
2815                                                .full_width()
2816                                                .key_binding(KeyBinding::for_action_in(
2817                                                    &ToggleModelSelector,
2818                                                    &focus_handle,
2819                                                    window,
2820                                                    cx,
2821                                                ))
2822                                                .on_click(|_event, window, cx| {
2823                                                    window.dispatch_action(
2824                                                        ToggleModelSelector.boxed_clone(),
2825                                                        cx,
2826                                                    )
2827                                                }),
2828                                        )
2829                                        .child(
2830                                            Button::new("settings", "View Settings")
2831                                                .label_size(LabelSize::Small)
2832                                                .icon(IconName::Settings)
2833                                                .icon_position(IconPosition::Start)
2834                                                .icon_size(IconSize::Small)
2835                                                .icon_color(Color::Muted)
2836                                                .full_width()
2837                                                .key_binding(KeyBinding::for_action_in(
2838                                                    &OpenSettings,
2839                                                    &focus_handle,
2840                                                    window,
2841                                                    cx,
2842                                                ))
2843                                                .on_click(|_event, window, cx| {
2844                                                    window.dispatch_action(
2845                                                        OpenSettings.boxed_clone(),
2846                                                        cx,
2847                                                    )
2848                                                }),
2849                                        ),
2850                                )
2851                        })
2852                        .when_some(configuration_error.as_ref(), |this, err| {
2853                            this.child(self.render_configuration_error(
2854                                err,
2855                                &focus_handle,
2856                                window,
2857                                cx,
2858                            ))
2859                        }),
2860                )
2861            })
2862            .when(!recent_history.is_empty(), |parent| {
2863                let focus_handle = focus_handle.clone();
2864                parent
2865                    .overflow_hidden()
2866                    .p_1p5()
2867                    .justify_end()
2868                    .gap_1()
2869                    .child(
2870                        self.render_empty_state_section_header(
2871                            "Recent",
2872                            Some(
2873                                Button::new("view-history", "View All")
2874                                    .style(ButtonStyle::Subtle)
2875                                    .label_size(LabelSize::Small)
2876                                    .key_binding(
2877                                        KeyBinding::for_action_in(
2878                                            &OpenHistory,
2879                                            &self.focus_handle(cx),
2880                                            window,
2881                                            cx,
2882                                        )
2883                                        .map(|kb| kb.size(rems_from_px(12.))),
2884                                    )
2885                                    .on_click(move |_event, window, cx| {
2886                                        window.dispatch_action(OpenHistory.boxed_clone(), cx);
2887                                    })
2888                                    .into_any_element(),
2889                            ),
2890                            cx,
2891                        ),
2892                    )
2893                    .child(
2894                        v_flex()
2895                            .gap_1()
2896                            .children(recent_history.into_iter().enumerate().map(
2897                                |(index, entry)| {
2898                                    // TODO: Add keyboard navigation.
2899                                    let is_hovered =
2900                                        self.hovered_recent_history_item == Some(index);
2901                                    HistoryEntryElement::new(entry.clone(), cx.entity().downgrade())
2902                                        .hovered(is_hovered)
2903                                        .on_hover(cx.listener(
2904                                            move |this, is_hovered, _window, cx| {
2905                                                if *is_hovered {
2906                                                    this.hovered_recent_history_item = Some(index);
2907                                                } else if this.hovered_recent_history_item
2908                                                    == Some(index)
2909                                                {
2910                                                    this.hovered_recent_history_item = None;
2911                                                }
2912                                                cx.notify();
2913                                            },
2914                                        ))
2915                                        .into_any_element()
2916                                },
2917                            )),
2918                    )
2919                    .when_some(configuration_error.as_ref(), |this, err| {
2920                        this.child(self.render_configuration_error(err, &focus_handle, window, cx))
2921                    })
2922            })
2923    }
2924
2925    fn render_configuration_error(
2926        &self,
2927        configuration_error: &ConfigurationError,
2928        focus_handle: &FocusHandle,
2929        window: &mut Window,
2930        cx: &mut App,
2931    ) -> impl IntoElement {
2932        match configuration_error {
2933            ConfigurationError::ModelNotFound
2934            | ConfigurationError::ProviderNotAuthenticated(_)
2935            | ConfigurationError::NoProvider => Banner::new()
2936                .severity(ui::Severity::Warning)
2937                .child(Label::new(configuration_error.to_string()))
2938                .action_slot(
2939                    Button::new("settings", "Configure Provider")
2940                        .style(ButtonStyle::Tinted(ui::TintColor::Warning))
2941                        .label_size(LabelSize::Small)
2942                        .key_binding(
2943                            KeyBinding::for_action_in(&OpenSettings, &focus_handle, window, cx)
2944                                .map(|kb| kb.size(rems_from_px(12.))),
2945                        )
2946                        .on_click(|_event, window, cx| {
2947                            window.dispatch_action(OpenSettings.boxed_clone(), cx)
2948                        }),
2949                ),
2950            ConfigurationError::ProviderPendingTermsAcceptance(provider) => {
2951                Banner::new().severity(ui::Severity::Warning).child(
2952                    h_flex().w_full().children(
2953                        provider.render_accept_terms(
2954                            LanguageModelProviderTosView::ThreadEmptyState,
2955                            cx,
2956                        ),
2957                    ),
2958                )
2959            }
2960        }
2961    }
2962
2963    fn render_tool_use_limit_reached(
2964        &self,
2965        window: &mut Window,
2966        cx: &mut Context<Self>,
2967    ) -> Option<AnyElement> {
2968        let active_thread = match &self.active_view {
2969            ActiveView::Thread { thread, .. } => thread,
2970            ActiveView::ExternalAgentThread { .. } => {
2971                return None;
2972            }
2973            ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {
2974                return None;
2975            }
2976        };
2977
2978        let thread = active_thread.read(cx).thread().read(cx);
2979
2980        let tool_use_limit_reached = thread.tool_use_limit_reached();
2981        if !tool_use_limit_reached {
2982            return None;
2983        }
2984
2985        let model = thread.configured_model()?.model;
2986
2987        let focus_handle = self.focus_handle(cx);
2988
2989        let banner = Banner::new()
2990            .severity(ui::Severity::Info)
2991            .child(Label::new("Consecutive tool use limit reached.").size(LabelSize::Small))
2992            .action_slot(
2993                h_flex()
2994                    .gap_1()
2995                    .child(
2996                        Button::new("continue-conversation", "Continue")
2997                            .layer(ElevationIndex::ModalSurface)
2998                            .label_size(LabelSize::Small)
2999                            .key_binding(
3000                                KeyBinding::for_action_in(
3001                                    &ContinueThread,
3002                                    &focus_handle,
3003                                    window,
3004                                    cx,
3005                                )
3006                                .map(|kb| kb.size(rems_from_px(10.))),
3007                            )
3008                            .on_click(cx.listener(|this, _, window, cx| {
3009                                this.continue_conversation(window, cx);
3010                            })),
3011                    )
3012                    .when(model.supports_burn_mode(), |this| {
3013                        this.child(
3014                            Button::new("continue-burn-mode", "Continue with Burn Mode")
3015                                .style(ButtonStyle::Filled)
3016                                .style(ButtonStyle::Tinted(ui::TintColor::Accent))
3017                                .layer(ElevationIndex::ModalSurface)
3018                                .label_size(LabelSize::Small)
3019                                .key_binding(
3020                                    KeyBinding::for_action_in(
3021                                        &ContinueWithBurnMode,
3022                                        &focus_handle,
3023                                        window,
3024                                        cx,
3025                                    )
3026                                    .map(|kb| kb.size(rems_from_px(10.))),
3027                                )
3028                                .tooltip(Tooltip::text("Enable Burn Mode for unlimited tool use."))
3029                                .on_click({
3030                                    let active_thread = active_thread.clone();
3031                                    cx.listener(move |this, _, window, cx| {
3032                                        active_thread.update(cx, |active_thread, cx| {
3033                                            active_thread.thread().update(cx, |thread, _cx| {
3034                                                thread.set_completion_mode(CompletionMode::Burn);
3035                                            });
3036                                        });
3037                                        this.continue_conversation(window, cx);
3038                                    })
3039                                }),
3040                        )
3041                    }),
3042            );
3043
3044        Some(div().px_2().pb_2().child(banner).into_any_element())
3045    }
3046
3047    fn create_copy_button(&self, message: impl Into<String>) -> impl IntoElement {
3048        let message = message.into();
3049
3050        IconButton::new("copy", IconName::Copy)
3051            .icon_size(IconSize::Small)
3052            .icon_color(Color::Muted)
3053            .tooltip(Tooltip::text("Copy Error Message"))
3054            .on_click(move |_, _, cx| {
3055                cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
3056            })
3057    }
3058
3059    fn dismiss_error_button(
3060        &self,
3061        thread: &Entity<ActiveThread>,
3062        cx: &mut Context<Self>,
3063    ) -> impl IntoElement {
3064        IconButton::new("dismiss", IconName::Close)
3065            .icon_size(IconSize::Small)
3066            .icon_color(Color::Muted)
3067            .tooltip(Tooltip::text("Dismiss Error"))
3068            .on_click(cx.listener({
3069                let thread = thread.clone();
3070                move |_, _, _, cx| {
3071                    thread.update(cx, |this, _cx| {
3072                        this.clear_last_error();
3073                    });
3074
3075                    cx.notify();
3076                }
3077            }))
3078    }
3079
3080    fn upgrade_button(
3081        &self,
3082        thread: &Entity<ActiveThread>,
3083        cx: &mut Context<Self>,
3084    ) -> impl IntoElement {
3085        Button::new("upgrade", "Upgrade")
3086            .label_size(LabelSize::Small)
3087            .style(ButtonStyle::Tinted(ui::TintColor::Accent))
3088            .on_click(cx.listener({
3089                let thread = thread.clone();
3090                move |_, _, _, cx| {
3091                    thread.update(cx, |this, _cx| {
3092                        this.clear_last_error();
3093                    });
3094
3095                    cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx));
3096                    cx.notify();
3097                }
3098            }))
3099    }
3100
3101    fn error_callout_bg(&self, cx: &Context<Self>) -> Hsla {
3102        cx.theme().status().error.opacity(0.08)
3103    }
3104
3105    fn render_payment_required_error(
3106        &self,
3107        thread: &Entity<ActiveThread>,
3108        cx: &mut Context<Self>,
3109    ) -> AnyElement {
3110        const ERROR_MESSAGE: &str =
3111            "You reached your free usage limit. Upgrade to Zed Pro for more prompts.";
3112
3113        let icon = Icon::new(IconName::XCircle)
3114            .size(IconSize::Small)
3115            .color(Color::Error);
3116
3117        div()
3118            .border_t_1()
3119            .border_color(cx.theme().colors().border)
3120            .child(
3121                Callout::new()
3122                    .icon(icon)
3123                    .title("Free Usage Exceeded")
3124                    .description(ERROR_MESSAGE)
3125                    .tertiary_action(self.upgrade_button(thread, cx))
3126                    .secondary_action(self.create_copy_button(ERROR_MESSAGE))
3127                    .primary_action(self.dismiss_error_button(thread, cx))
3128                    .bg_color(self.error_callout_bg(cx)),
3129            )
3130            .into_any_element()
3131    }
3132
3133    fn render_model_request_limit_reached_error(
3134        &self,
3135        plan: Plan,
3136        thread: &Entity<ActiveThread>,
3137        cx: &mut Context<Self>,
3138    ) -> AnyElement {
3139        let error_message = match plan {
3140            Plan::ZedPro => "Upgrade to usage-based billing for more prompts.",
3141            Plan::ZedProTrial | Plan::ZedFree => "Upgrade to Zed Pro for more prompts.",
3142        };
3143
3144        let icon = Icon::new(IconName::XCircle)
3145            .size(IconSize::Small)
3146            .color(Color::Error);
3147
3148        div()
3149            .border_t_1()
3150            .border_color(cx.theme().colors().border)
3151            .child(
3152                Callout::new()
3153                    .icon(icon)
3154                    .title("Model Prompt Limit Reached")
3155                    .description(error_message)
3156                    .tertiary_action(self.upgrade_button(thread, cx))
3157                    .secondary_action(self.create_copy_button(error_message))
3158                    .primary_action(self.dismiss_error_button(thread, cx))
3159                    .bg_color(self.error_callout_bg(cx)),
3160            )
3161            .into_any_element()
3162    }
3163
3164    fn render_error_message(
3165        &self,
3166        header: SharedString,
3167        message: SharedString,
3168        thread: &Entity<ActiveThread>,
3169        cx: &mut Context<Self>,
3170    ) -> AnyElement {
3171        let message_with_header = format!("{}\n{}", header, message);
3172
3173        let icon = Icon::new(IconName::XCircle)
3174            .size(IconSize::Small)
3175            .color(Color::Error);
3176
3177        let retry_button = Button::new("retry", "Retry")
3178            .icon(IconName::RotateCw)
3179            .icon_position(IconPosition::Start)
3180            .icon_size(IconSize::Small)
3181            .label_size(LabelSize::Small)
3182            .on_click({
3183                let thread = thread.clone();
3184                move |_, window, cx| {
3185                    thread.update(cx, |thread, cx| {
3186                        thread.clear_last_error();
3187                        thread.thread().update(cx, |thread, cx| {
3188                            thread.retry_last_completion(Some(window.window_handle()), cx);
3189                        });
3190                    });
3191                }
3192            });
3193
3194        div()
3195            .border_t_1()
3196            .border_color(cx.theme().colors().border)
3197            .child(
3198                Callout::new()
3199                    .icon(icon)
3200                    .title(header)
3201                    .description(message.clone())
3202                    .primary_action(retry_button)
3203                    .secondary_action(self.dismiss_error_button(thread, cx))
3204                    .tertiary_action(self.create_copy_button(message_with_header))
3205                    .bg_color(self.error_callout_bg(cx)),
3206            )
3207            .into_any_element()
3208    }
3209
3210    fn render_retryable_error(
3211        &self,
3212        message: SharedString,
3213        can_enable_burn_mode: bool,
3214        thread: &Entity<ActiveThread>,
3215        cx: &mut Context<Self>,
3216    ) -> AnyElement {
3217        let icon = Icon::new(IconName::XCircle)
3218            .size(IconSize::Small)
3219            .color(Color::Error);
3220
3221        let retry_button = Button::new("retry", "Retry")
3222            .icon(IconName::RotateCw)
3223            .icon_position(IconPosition::Start)
3224            .icon_size(IconSize::Small)
3225            .label_size(LabelSize::Small)
3226            .on_click({
3227                let thread = thread.clone();
3228                move |_, window, cx| {
3229                    thread.update(cx, |thread, cx| {
3230                        thread.clear_last_error();
3231                        thread.thread().update(cx, |thread, cx| {
3232                            thread.retry_last_completion(Some(window.window_handle()), cx);
3233                        });
3234                    });
3235                }
3236            });
3237
3238        let mut callout = Callout::new()
3239            .icon(icon)
3240            .title("Error")
3241            .description(message.clone())
3242            .bg_color(self.error_callout_bg(cx))
3243            .primary_action(retry_button);
3244
3245        if can_enable_burn_mode {
3246            let burn_mode_button = Button::new("enable_burn_retry", "Enable Burn Mode and Retry")
3247                .icon(IconName::ZedBurnMode)
3248                .icon_position(IconPosition::Start)
3249                .icon_size(IconSize::Small)
3250                .label_size(LabelSize::Small)
3251                .on_click({
3252                    let thread = thread.clone();
3253                    move |_, window, cx| {
3254                        thread.update(cx, |thread, cx| {
3255                            thread.clear_last_error();
3256                            thread.thread().update(cx, |thread, cx| {
3257                                thread.enable_burn_mode_and_retry(Some(window.window_handle()), cx);
3258                            });
3259                        });
3260                    }
3261                });
3262            callout = callout.secondary_action(burn_mode_button);
3263        }
3264
3265        div()
3266            .border_t_1()
3267            .border_color(cx.theme().colors().border)
3268            .child(callout)
3269            .into_any_element()
3270    }
3271
3272    fn render_prompt_editor(
3273        &self,
3274        context_editor: &Entity<TextThreadEditor>,
3275        buffer_search_bar: &Entity<BufferSearchBar>,
3276        window: &mut Window,
3277        cx: &mut Context<Self>,
3278    ) -> Div {
3279        let mut registrar = buffer_search::DivRegistrar::new(
3280            |this, _, _cx| match &this.active_view {
3281                ActiveView::TextThread {
3282                    buffer_search_bar, ..
3283                } => Some(buffer_search_bar.clone()),
3284                _ => None,
3285            },
3286            cx,
3287        );
3288        BufferSearchBar::register(&mut registrar);
3289        registrar
3290            .into_div()
3291            .size_full()
3292            .relative()
3293            .map(|parent| {
3294                buffer_search_bar.update(cx, |buffer_search_bar, cx| {
3295                    if buffer_search_bar.is_dismissed() {
3296                        return parent;
3297                    }
3298                    parent.child(
3299                        div()
3300                            .p(DynamicSpacing::Base08.rems(cx))
3301                            .border_b_1()
3302                            .border_color(cx.theme().colors().border_variant)
3303                            .bg(cx.theme().colors().editor_background)
3304                            .child(buffer_search_bar.render(window, cx)),
3305                    )
3306                })
3307            })
3308            .child(context_editor.clone())
3309            .child(self.render_drag_target(cx))
3310    }
3311
3312    fn render_drag_target(&self, cx: &Context<Self>) -> Div {
3313        let is_local = self.project.read(cx).is_local();
3314        div()
3315            .invisible()
3316            .absolute()
3317            .top_0()
3318            .right_0()
3319            .bottom_0()
3320            .left_0()
3321            .bg(cx.theme().colors().drop_target_background)
3322            .drag_over::<DraggedTab>(|this, _, _, _| this.visible())
3323            .drag_over::<DraggedSelection>(|this, _, _, _| this.visible())
3324            .when(is_local, |this| {
3325                this.drag_over::<ExternalPaths>(|this, _, _, _| this.visible())
3326            })
3327            .on_drop(cx.listener(move |this, tab: &DraggedTab, window, cx| {
3328                let item = tab.pane.read(cx).item_for_index(tab.ix);
3329                let project_paths = item
3330                    .and_then(|item| item.project_path(cx))
3331                    .into_iter()
3332                    .collect::<Vec<_>>();
3333                this.handle_drop(project_paths, vec![], window, cx);
3334            }))
3335            .on_drop(
3336                cx.listener(move |this, selection: &DraggedSelection, window, cx| {
3337                    let project_paths = selection
3338                        .items()
3339                        .filter_map(|item| this.project.read(cx).path_for_entry(item.entry_id, cx))
3340                        .collect::<Vec<_>>();
3341                    this.handle_drop(project_paths, vec![], window, cx);
3342                }),
3343            )
3344            .on_drop(cx.listener(move |this, paths: &ExternalPaths, window, cx| {
3345                let tasks = paths
3346                    .paths()
3347                    .into_iter()
3348                    .map(|path| {
3349                        Workspace::project_path_for_path(this.project.clone(), &path, false, cx)
3350                    })
3351                    .collect::<Vec<_>>();
3352                cx.spawn_in(window, async move |this, cx| {
3353                    let mut paths = vec![];
3354                    let mut added_worktrees = vec![];
3355                    let opened_paths = futures::future::join_all(tasks).await;
3356                    for entry in opened_paths {
3357                        if let Some((worktree, project_path)) = entry.log_err() {
3358                            added_worktrees.push(worktree);
3359                            paths.push(project_path);
3360                        }
3361                    }
3362                    this.update_in(cx, |this, window, cx| {
3363                        this.handle_drop(paths, added_worktrees, window, cx);
3364                    })
3365                    .ok();
3366                })
3367                .detach();
3368            }))
3369    }
3370
3371    fn handle_drop(
3372        &mut self,
3373        paths: Vec<ProjectPath>,
3374        added_worktrees: Vec<Entity<Worktree>>,
3375        window: &mut Window,
3376        cx: &mut Context<Self>,
3377    ) {
3378        match &self.active_view {
3379            ActiveView::Thread { thread, .. } => {
3380                let context_store = thread.read(cx).context_store().clone();
3381                context_store.update(cx, move |context_store, cx| {
3382                    let mut tasks = Vec::new();
3383                    for project_path in &paths {
3384                        tasks.push(context_store.add_file_from_path(
3385                            project_path.clone(),
3386                            false,
3387                            cx,
3388                        ));
3389                    }
3390                    cx.background_spawn(async move {
3391                        futures::future::join_all(tasks).await;
3392                        // Need to hold onto the worktrees until they have already been used when
3393                        // opening the buffers.
3394                        drop(added_worktrees);
3395                    })
3396                    .detach();
3397                });
3398            }
3399            ActiveView::ExternalAgentThread { thread_view } => {
3400                thread_view.update(cx, |thread_view, cx| {
3401                    thread_view.insert_dragged_files(paths, added_worktrees, window, cx);
3402                });
3403            }
3404            ActiveView::TextThread { context_editor, .. } => {
3405                context_editor.update(cx, |context_editor, cx| {
3406                    TextThreadEditor::insert_dragged_files(
3407                        context_editor,
3408                        paths,
3409                        added_worktrees,
3410                        window,
3411                        cx,
3412                    );
3413                });
3414            }
3415            ActiveView::History | ActiveView::Configuration => {}
3416        }
3417    }
3418
3419    fn key_context(&self) -> KeyContext {
3420        let mut key_context = KeyContext::new_with_defaults();
3421        key_context.add("AgentPanel");
3422        match &self.active_view {
3423            ActiveView::ExternalAgentThread { .. } => key_context.add("external_agent_thread"),
3424            ActiveView::TextThread { .. } => key_context.add("prompt_editor"),
3425            ActiveView::Thread { .. } | ActiveView::History | ActiveView::Configuration => {}
3426        }
3427        key_context
3428    }
3429}
3430
3431impl Render for AgentPanel {
3432    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3433        // WARNING: Changes to this element hierarchy can have
3434        // non-obvious implications to the layout of children.
3435        //
3436        // If you need to change it, please confirm:
3437        // - The message editor expands (cmd-option-esc) correctly
3438        // - When expanded, the buttons at the bottom of the panel are displayed correctly
3439        // - Font size works as expected and can be changed with cmd-+/cmd-
3440        // - Scrolling in all views works as expected
3441        // - Files can be dropped into the panel
3442        let content = v_flex()
3443            .relative()
3444            .size_full()
3445            .justify_between()
3446            .key_context(self.key_context())
3447            .on_action(cx.listener(Self::cancel))
3448            .on_action(cx.listener(|this, action: &NewThread, window, cx| {
3449                this.new_thread(action, window, cx);
3450            }))
3451            .on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
3452                this.open_history(window, cx);
3453            }))
3454            .on_action(cx.listener(|this, _: &OpenSettings, window, cx| {
3455                this.open_configuration(window, cx);
3456            }))
3457            .on_action(cx.listener(Self::open_active_thread_as_markdown))
3458            .on_action(cx.listener(Self::deploy_rules_library))
3459            .on_action(cx.listener(Self::open_agent_diff))
3460            .on_action(cx.listener(Self::go_back))
3461            .on_action(cx.listener(Self::toggle_navigation_menu))
3462            .on_action(cx.listener(Self::toggle_options_menu))
3463            .on_action(cx.listener(Self::increase_font_size))
3464            .on_action(cx.listener(Self::decrease_font_size))
3465            .on_action(cx.listener(Self::reset_font_size))
3466            .on_action(cx.listener(Self::toggle_zoom))
3467            .on_action(cx.listener(|this, _: &ContinueThread, window, cx| {
3468                this.continue_conversation(window, cx);
3469            }))
3470            .on_action(cx.listener(|this, _: &ContinueWithBurnMode, window, cx| {
3471                match &this.active_view {
3472                    ActiveView::Thread { thread, .. } => {
3473                        thread.update(cx, |active_thread, cx| {
3474                            active_thread.thread().update(cx, |thread, _cx| {
3475                                thread.set_completion_mode(CompletionMode::Burn);
3476                            });
3477                        });
3478                        this.continue_conversation(window, cx);
3479                    }
3480                    ActiveView::ExternalAgentThread { .. } => {}
3481                    ActiveView::TextThread { .. }
3482                    | ActiveView::History
3483                    | ActiveView::Configuration => {}
3484                }
3485            }))
3486            .on_action(cx.listener(Self::toggle_burn_mode))
3487            .child(self.render_toolbar(window, cx))
3488            .children(self.render_onboarding(window, cx))
3489            .map(|parent| match &self.active_view {
3490                ActiveView::Thread {
3491                    thread,
3492                    message_editor,
3493                    ..
3494                } => parent
3495                    .child(
3496                        if thread.read(cx).is_empty() && !self.should_render_onboarding(cx) {
3497                            self.render_thread_empty_state(window, cx)
3498                                .into_any_element()
3499                        } else {
3500                            thread.clone().into_any_element()
3501                        },
3502                    )
3503                    .children(self.render_tool_use_limit_reached(window, cx))
3504                    .when_some(thread.read(cx).last_error(), |this, last_error| {
3505                        this.child(
3506                            div()
3507                                .child(match last_error {
3508                                    ThreadError::PaymentRequired => {
3509                                        self.render_payment_required_error(thread, cx)
3510                                    }
3511                                    ThreadError::ModelRequestLimitReached { plan } => self
3512                                        .render_model_request_limit_reached_error(plan, thread, cx),
3513                                    ThreadError::Message { header, message } => {
3514                                        self.render_error_message(header, message, thread, cx)
3515                                    }
3516                                    ThreadError::RetryableError {
3517                                        message,
3518                                        can_enable_burn_mode,
3519                                    } => self.render_retryable_error(
3520                                        message,
3521                                        can_enable_burn_mode,
3522                                        thread,
3523                                        cx,
3524                                    ),
3525                                })
3526                                .into_any(),
3527                        )
3528                    })
3529                    .child(h_flex().relative().child(message_editor.clone()).when(
3530                        !LanguageModelRegistry::read_global(cx).has_authenticated_provider(cx),
3531                        |this| this.child(self.render_backdrop(cx)),
3532                    ))
3533                    .child(self.render_drag_target(cx)),
3534                ActiveView::ExternalAgentThread { thread_view, .. } => parent
3535                    .child(thread_view.clone())
3536                    .child(self.render_drag_target(cx)),
3537                ActiveView::History => {
3538                    if cx.has_flag::<feature_flags::AcpFeatureFlag>() {
3539                        parent.child(self.acp_history.clone())
3540                    } else {
3541                        parent.child(self.history.clone())
3542                    }
3543                }
3544                ActiveView::TextThread {
3545                    context_editor,
3546                    buffer_search_bar,
3547                    ..
3548                } => {
3549                    let model_registry = LanguageModelRegistry::read_global(cx);
3550                    let configuration_error =
3551                        model_registry.configuration_error(model_registry.default_model(), cx);
3552                    parent
3553                        .map(|this| {
3554                            if !self.should_render_onboarding(cx)
3555                                && let Some(err) = configuration_error.as_ref()
3556                            {
3557                                this.child(
3558                                    div().bg(cx.theme().colors().editor_background).p_2().child(
3559                                        self.render_configuration_error(
3560                                            err,
3561                                            &self.focus_handle(cx),
3562                                            window,
3563                                            cx,
3564                                        ),
3565                                    ),
3566                                )
3567                            } else {
3568                                this
3569                            }
3570                        })
3571                        .child(self.render_prompt_editor(
3572                            context_editor,
3573                            buffer_search_bar,
3574                            window,
3575                            cx,
3576                        ))
3577                }
3578                ActiveView::Configuration => parent.children(self.configuration.clone()),
3579            })
3580            .children(self.render_trial_end_upsell(window, cx));
3581
3582        match self.active_view.which_font_size_used() {
3583            WhichFontSize::AgentFont => {
3584                WithRemSize::new(ThemeSettings::get_global(cx).agent_font_size(cx))
3585                    .size_full()
3586                    .child(content)
3587                    .into_any()
3588            }
3589            _ => content.into_any(),
3590        }
3591    }
3592}
3593
3594struct PromptLibraryInlineAssist {
3595    workspace: WeakEntity<Workspace>,
3596}
3597
3598impl PromptLibraryInlineAssist {
3599    pub fn new(workspace: WeakEntity<Workspace>) -> Self {
3600        Self { workspace }
3601    }
3602}
3603
3604impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
3605    fn assist(
3606        &self,
3607        prompt_editor: &Entity<Editor>,
3608        initial_prompt: Option<String>,
3609        window: &mut Window,
3610        cx: &mut Context<RulesLibrary>,
3611    ) {
3612        InlineAssistant::update_global(cx, |assistant, cx| {
3613            let Some(project) = self
3614                .workspace
3615                .upgrade()
3616                .map(|workspace| workspace.read(cx).project().downgrade())
3617            else {
3618                return;
3619            };
3620            let prompt_store = None;
3621            let thread_store = None;
3622            let text_thread_store = None;
3623            let context_store = cx.new(|_| ContextStore::new(project.clone(), None));
3624            assistant.assist(
3625                &prompt_editor,
3626                self.workspace.clone(),
3627                context_store,
3628                project,
3629                prompt_store,
3630                thread_store,
3631                text_thread_store,
3632                initial_prompt,
3633                window,
3634                cx,
3635            )
3636        })
3637    }
3638
3639    fn focus_agent_panel(
3640        &self,
3641        workspace: &mut Workspace,
3642        window: &mut Window,
3643        cx: &mut Context<Workspace>,
3644    ) -> bool {
3645        workspace.focus_panel::<AgentPanel>(window, cx).is_some()
3646    }
3647}
3648
3649pub struct ConcreteAssistantPanelDelegate;
3650
3651impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
3652    fn active_context_editor(
3653        &self,
3654        workspace: &mut Workspace,
3655        _window: &mut Window,
3656        cx: &mut Context<Workspace>,
3657    ) -> Option<Entity<TextThreadEditor>> {
3658        let panel = workspace.panel::<AgentPanel>(cx)?;
3659        panel.read(cx).active_context_editor()
3660    }
3661
3662    fn open_saved_context(
3663        &self,
3664        workspace: &mut Workspace,
3665        path: Arc<Path>,
3666        window: &mut Window,
3667        cx: &mut Context<Workspace>,
3668    ) -> Task<Result<()>> {
3669        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
3670            return Task::ready(Err(anyhow!("Agent panel not found")));
3671        };
3672
3673        panel.update(cx, |panel, cx| {
3674            panel.open_saved_prompt_editor(path, window, cx)
3675        })
3676    }
3677
3678    fn open_remote_context(
3679        &self,
3680        _workspace: &mut Workspace,
3681        _context_id: assistant_context::ContextId,
3682        _window: &mut Window,
3683        _cx: &mut Context<Workspace>,
3684    ) -> Task<Result<Entity<TextThreadEditor>>> {
3685        Task::ready(Err(anyhow!("opening remote context not implemented")))
3686    }
3687
3688    fn quote_selection(
3689        &self,
3690        workspace: &mut Workspace,
3691        selection_ranges: Vec<Range<Anchor>>,
3692        buffer: Entity<MultiBuffer>,
3693        window: &mut Window,
3694        cx: &mut Context<Workspace>,
3695    ) {
3696        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
3697            return;
3698        };
3699
3700        if !panel.focus_handle(cx).contains_focused(window, cx) {
3701            workspace.toggle_panel_focus::<AgentPanel>(window, cx);
3702        }
3703
3704        panel.update(cx, |_, cx| {
3705            // Wait to create a new context until the workspace is no longer
3706            // being updated.
3707            cx.defer_in(window, move |panel, window, cx| {
3708                if let Some(message_editor) = panel.active_message_editor() {
3709                    message_editor.update(cx, |message_editor, cx| {
3710                        message_editor.context_store().update(cx, |store, cx| {
3711                            let buffer = buffer.read(cx);
3712                            let selection_ranges = selection_ranges
3713                                .into_iter()
3714                                .flat_map(|range| {
3715                                    let (start_buffer, start) =
3716                                        buffer.text_anchor_for_position(range.start, cx)?;
3717                                    let (end_buffer, end) =
3718                                        buffer.text_anchor_for_position(range.end, cx)?;
3719                                    if start_buffer != end_buffer {
3720                                        return None;
3721                                    }
3722                                    Some((start_buffer, start..end))
3723                                })
3724                                .collect::<Vec<_>>();
3725
3726                            for (buffer, range) in selection_ranges {
3727                                store.add_selection(buffer, range, cx);
3728                            }
3729                        })
3730                    })
3731                } else if let Some(context_editor) = panel.active_context_editor() {
3732                    let snapshot = buffer.read(cx).snapshot(cx);
3733                    let selection_ranges = selection_ranges
3734                        .into_iter()
3735                        .map(|range| range.to_point(&snapshot))
3736                        .collect::<Vec<_>>();
3737
3738                    context_editor.update(cx, |context_editor, cx| {
3739                        context_editor.quote_ranges(selection_ranges, snapshot, window, cx)
3740                    });
3741                }
3742            });
3743        });
3744    }
3745}
3746
3747struct OnboardingUpsell;
3748
3749impl Dismissable for OnboardingUpsell {
3750    const KEY: &'static str = "dismissed-trial-upsell";
3751}
3752
3753struct TrialEndUpsell;
3754
3755impl Dismissable for TrialEndUpsell {
3756    const KEY: &'static str = "dismissed-trial-end-upsell";
3757}