agent_panel.rs

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