agent_panel.rs

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