agent_panel.rs

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