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                            }),
1925                        )
1926                        .action("Add Custom Server…", Box::new(AddContextServer))
1927                        .separator();
1928
1929                    menu = menu
1930                        .action("Rules…", Box::new(OpenRulesLibrary::default()))
1931                        .action("Settings", Box::new(OpenConfiguration))
1932                        .action(zoom_in_label, Box::new(ToggleZoom));
1933                    menu
1934                }))
1935            });
1936
1937        h_flex()
1938            .id("assistant-toolbar")
1939            .h(Tab::container_height(cx))
1940            .max_w_full()
1941            .flex_none()
1942            .justify_between()
1943            .gap_2()
1944            .bg(cx.theme().colors().tab_bar_background)
1945            .border_b_1()
1946            .border_color(cx.theme().colors().border)
1947            .child(
1948                h_flex()
1949                    .size_full()
1950                    .pl_1()
1951                    .gap_1()
1952                    .child(match &self.active_view {
1953                        ActiveView::History | ActiveView::Configuration => go_back_button,
1954                        _ => recent_entries_menu,
1955                    })
1956                    .child(self.render_title_view(window, cx)),
1957            )
1958            .child(
1959                h_flex()
1960                    .h_full()
1961                    .gap_2()
1962                    .children(self.render_token_count(cx))
1963                    .child(
1964                        h_flex()
1965                            .h_full()
1966                            .gap(DynamicSpacing::Base02.rems(cx))
1967                            .px(DynamicSpacing::Base08.rems(cx))
1968                            .border_l_1()
1969                            .border_color(cx.theme().colors().border)
1970                            .child(new_thread_menu)
1971                            .child(agent_panel_menu),
1972                    ),
1973            )
1974    }
1975
1976    fn render_token_count(&self, cx: &App) -> Option<AnyElement> {
1977        let (active_thread, message_editor) = match &self.active_view {
1978            ActiveView::Thread {
1979                thread,
1980                message_editor,
1981                ..
1982            } => (thread.read(cx), message_editor.read(cx)),
1983            ActiveView::AcpThread { .. } => {
1984                return None;
1985            }
1986            ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {
1987                return None;
1988            }
1989        };
1990
1991        let editor_empty = message_editor.is_editor_fully_empty(cx);
1992
1993        if active_thread.is_empty() && editor_empty {
1994            return None;
1995        }
1996
1997        let thread = active_thread.thread().read(cx);
1998        let is_generating = thread.is_generating();
1999        let conversation_token_usage = thread.total_token_usage()?;
2000
2001        let (total_token_usage, is_estimating) =
2002            if let Some((editing_message_id, unsent_tokens)) = active_thread.editing_message_id() {
2003                let combined = thread
2004                    .token_usage_up_to_message(editing_message_id)
2005                    .add(unsent_tokens);
2006
2007                (combined, unsent_tokens > 0)
2008            } else {
2009                let unsent_tokens = message_editor.last_estimated_token_count().unwrap_or(0);
2010                let combined = conversation_token_usage.add(unsent_tokens);
2011
2012                (combined, unsent_tokens > 0)
2013            };
2014
2015        let is_waiting_to_update_token_count = message_editor.is_waiting_to_update_token_count();
2016
2017        match &self.active_view {
2018            ActiveView::Thread { .. } => {
2019                if total_token_usage.total == 0 {
2020                    return None;
2021                }
2022
2023                let token_color = match total_token_usage.ratio() {
2024                    TokenUsageRatio::Normal if is_estimating => Color::Default,
2025                    TokenUsageRatio::Normal => Color::Muted,
2026                    TokenUsageRatio::Warning => Color::Warning,
2027                    TokenUsageRatio::Exceeded => Color::Error,
2028                };
2029
2030                let token_count = h_flex()
2031                    .id("token-count")
2032                    .flex_shrink_0()
2033                    .gap_0p5()
2034                    .when(!is_generating && is_estimating, |parent| {
2035                        parent
2036                            .child(
2037                                h_flex()
2038                                    .mr_1()
2039                                    .size_2p5()
2040                                    .justify_center()
2041                                    .rounded_full()
2042                                    .bg(cx.theme().colors().text.opacity(0.1))
2043                                    .child(
2044                                        div().size_1().rounded_full().bg(cx.theme().colors().text),
2045                                    ),
2046                            )
2047                            .tooltip(move |window, cx| {
2048                                Tooltip::with_meta(
2049                                    "Estimated New Token Count",
2050                                    None,
2051                                    format!(
2052                                        "Current Conversation Tokens: {}",
2053                                        humanize_token_count(conversation_token_usage.total)
2054                                    ),
2055                                    window,
2056                                    cx,
2057                                )
2058                            })
2059                    })
2060                    .child(
2061                        Label::new(humanize_token_count(total_token_usage.total))
2062                            .size(LabelSize::Small)
2063                            .color(token_color)
2064                            .map(|label| {
2065                                if is_generating || is_waiting_to_update_token_count {
2066                                    label
2067                                        .with_animation(
2068                                            "used-tokens-label",
2069                                            Animation::new(Duration::from_secs(2))
2070                                                .repeat()
2071                                                .with_easing(pulsating_between(0.6, 1.)),
2072                                            |label, delta| label.alpha(delta),
2073                                        )
2074                                        .into_any()
2075                                } else {
2076                                    label.into_any_element()
2077                                }
2078                            }),
2079                    )
2080                    .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
2081                    .child(
2082                        Label::new(humanize_token_count(total_token_usage.max))
2083                            .size(LabelSize::Small)
2084                            .color(Color::Muted),
2085                    )
2086                    .into_any();
2087
2088                Some(token_count)
2089            }
2090            ActiveView::TextThread { context_editor, .. } => {
2091                let element = render_remaining_tokens(context_editor, cx)?;
2092
2093                Some(element.into_any_element())
2094            }
2095            _ => None,
2096        }
2097    }
2098
2099    fn should_render_trial_end_upsell(&self, cx: &mut Context<Self>) -> bool {
2100        if TrialEndUpsell::dismissed() {
2101            return false;
2102        }
2103
2104        let plan = self.user_store.read(cx).current_plan();
2105        let has_previous_trial = self.user_store.read(cx).trial_started_at().is_some();
2106
2107        matches!(plan, Some(Plan::Free)) && has_previous_trial
2108    }
2109
2110    fn should_render_upsell(&self, cx: &mut Context<Self>) -> bool {
2111        match &self.active_view {
2112            ActiveView::Thread { thread, .. } => {
2113                let is_using_zed_provider = thread
2114                    .read(cx)
2115                    .thread()
2116                    .read(cx)
2117                    .configured_model()
2118                    .map_or(false, |model| model.provider.id() == ZED_CLOUD_PROVIDER_ID);
2119
2120                if !is_using_zed_provider {
2121                    return false;
2122                }
2123            }
2124            ActiveView::AcpThread { .. } => {
2125                return false;
2126            }
2127            ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {
2128                return false;
2129            }
2130        };
2131
2132        if self.hide_upsell || Upsell::dismissed() {
2133            return false;
2134        }
2135
2136        let plan = self.user_store.read(cx).current_plan();
2137        if matches!(plan, Some(Plan::ZedPro | Plan::ZedProTrial)) {
2138            return false;
2139        }
2140
2141        let has_previous_trial = self.user_store.read(cx).trial_started_at().is_some();
2142        if has_previous_trial {
2143            return false;
2144        }
2145
2146        true
2147    }
2148
2149    fn render_upsell(
2150        &self,
2151        _window: &mut Window,
2152        cx: &mut Context<Self>,
2153    ) -> Option<impl IntoElement> {
2154        if !self.should_render_upsell(cx) {
2155            return None;
2156        }
2157
2158        if self.user_store.read(cx).account_too_young() {
2159            Some(self.render_young_account_upsell(cx).into_any_element())
2160        } else {
2161            Some(self.render_trial_upsell(cx).into_any_element())
2162        }
2163    }
2164
2165    fn render_young_account_upsell(&self, cx: &mut Context<Self>) -> impl IntoElement {
2166        let checkbox = CheckboxWithLabel::new(
2167            "dont-show-again",
2168            Label::new("Don't show again").color(Color::Muted),
2169            ToggleState::Unselected,
2170            move |toggle_state, _window, cx| {
2171                let toggle_state_bool = toggle_state.selected();
2172
2173                Upsell::set_dismissed(toggle_state_bool, cx);
2174            },
2175        );
2176
2177        let contents = div()
2178            .size_full()
2179            .gap_2()
2180            .flex()
2181            .flex_col()
2182            .child(Headline::new("Build better with Zed Pro").size(HeadlineSize::Small))
2183            .child(
2184                Label::new("Your GitHub account was created less than 30 days ago, so we can't offer you a free trial.")
2185                    .size(LabelSize::Small),
2186            )
2187            .child(
2188                Label::new(
2189                    "Use your own API keys, upgrade to Zed Pro or send an email to billing-support@zed.dev.",
2190                )
2191                .color(Color::Muted),
2192            )
2193            .child(
2194                h_flex()
2195                    .w_full()
2196                    .px_neg_1()
2197                    .justify_between()
2198                    .items_center()
2199                    .child(h_flex().items_center().gap_1().child(checkbox))
2200                    .child(
2201                        h_flex()
2202                            .gap_2()
2203                            .child(
2204                                Button::new("dismiss-button", "Not Now")
2205                                    .style(ButtonStyle::Transparent)
2206                                    .color(Color::Muted)
2207                                    .on_click({
2208                                        let agent_panel = cx.entity();
2209                                        move |_, _, cx| {
2210                                            agent_panel.update(cx, |this, cx| {
2211                                                this.hide_upsell = true;
2212                                                cx.notify();
2213                                            });
2214                                        }
2215                                    }),
2216                            )
2217                            .child(
2218                                Button::new("cta-button", "Upgrade to Zed Pro")
2219                                    .style(ButtonStyle::Transparent)
2220                                    .on_click(|_, _, cx| cx.open_url(&zed_urls::account_url(cx))),
2221                            ),
2222                    ),
2223            );
2224
2225        self.render_upsell_container(cx, contents)
2226    }
2227
2228    fn render_trial_upsell(&self, cx: &mut Context<Self>) -> impl IntoElement {
2229        let checkbox = CheckboxWithLabel::new(
2230            "dont-show-again",
2231            Label::new("Don't show again").color(Color::Muted),
2232            ToggleState::Unselected,
2233            move |toggle_state, _window, cx| {
2234                let toggle_state_bool = toggle_state.selected();
2235
2236                Upsell::set_dismissed(toggle_state_bool, cx);
2237            },
2238        );
2239
2240        let contents = div()
2241            .size_full()
2242            .gap_2()
2243            .flex()
2244            .flex_col()
2245            .child(Headline::new("Build better with Zed Pro").size(HeadlineSize::Small))
2246            .child(
2247                Label::new("Try Zed Pro for free for 14 days - no credit card required.")
2248                    .size(LabelSize::Small),
2249            )
2250            .child(
2251                Label::new(
2252                    "Use your own API keys or enable usage-based billing once you hit the cap.",
2253                )
2254                .color(Color::Muted),
2255            )
2256            .child(
2257                h_flex()
2258                    .w_full()
2259                    .px_neg_1()
2260                    .justify_between()
2261                    .items_center()
2262                    .child(h_flex().items_center().gap_1().child(checkbox))
2263                    .child(
2264                        h_flex()
2265                            .gap_2()
2266                            .child(
2267                                Button::new("dismiss-button", "Not Now")
2268                                    .style(ButtonStyle::Transparent)
2269                                    .color(Color::Muted)
2270                                    .on_click({
2271                                        let agent_panel = cx.entity();
2272                                        move |_, _, cx| {
2273                                            agent_panel.update(cx, |this, cx| {
2274                                                this.hide_upsell = true;
2275                                                cx.notify();
2276                                            });
2277                                        }
2278                                    }),
2279                            )
2280                            .child(
2281                                Button::new("cta-button", "Start Trial")
2282                                    .style(ButtonStyle::Transparent)
2283                                    .on_click(|_, _, cx| cx.open_url(&zed_urls::account_url(cx))),
2284                            ),
2285                    ),
2286            );
2287
2288        self.render_upsell_container(cx, contents)
2289    }
2290
2291    fn render_trial_end_upsell(
2292        &self,
2293        _window: &mut Window,
2294        cx: &mut Context<Self>,
2295    ) -> Option<impl IntoElement> {
2296        if !self.should_render_trial_end_upsell(cx) {
2297            return None;
2298        }
2299
2300        Some(
2301            self.render_upsell_container(
2302                cx,
2303                div()
2304                    .size_full()
2305                    .gap_2()
2306                    .flex()
2307                    .flex_col()
2308                    .child(
2309                        Headline::new("Your Zed Pro trial has expired.").size(HeadlineSize::Small),
2310                    )
2311                    .child(
2312                        Label::new("You've been automatically reset to the free plan.")
2313                            .size(LabelSize::Small),
2314                    )
2315                    .child(
2316                        h_flex()
2317                            .w_full()
2318                            .px_neg_1()
2319                            .justify_between()
2320                            .items_center()
2321                            .child(div())
2322                            .child(
2323                                h_flex()
2324                                    .gap_2()
2325                                    .child(
2326                                        Button::new("dismiss-button", "Stay on Free")
2327                                            .style(ButtonStyle::Transparent)
2328                                            .color(Color::Muted)
2329                                            .on_click({
2330                                                let agent_panel = cx.entity();
2331                                                move |_, _, cx| {
2332                                                    agent_panel.update(cx, |_this, cx| {
2333                                                        TrialEndUpsell::set_dismissed(true, cx);
2334                                                        cx.notify();
2335                                                    });
2336                                                }
2337                                            }),
2338                                    )
2339                                    .child(
2340                                        Button::new("cta-button", "Upgrade to Zed Pro")
2341                                            .style(ButtonStyle::Transparent)
2342                                            .on_click(|_, _, cx| {
2343                                                cx.open_url(&zed_urls::account_url(cx))
2344                                            }),
2345                                    ),
2346                            ),
2347                    ),
2348            ),
2349        )
2350    }
2351
2352    fn render_upsell_container(&self, cx: &mut Context<Self>, content: Div) -> Div {
2353        div().p_2().child(
2354            v_flex()
2355                .w_full()
2356                .elevation_2(cx)
2357                .rounded(px(8.))
2358                .bg(cx.theme().colors().background.alpha(0.5))
2359                .p(px(3.))
2360                .child(
2361                    div()
2362                        .gap_2()
2363                        .flex()
2364                        .flex_col()
2365                        .size_full()
2366                        .border_1()
2367                        .rounded(px(5.))
2368                        .border_color(cx.theme().colors().text.alpha(0.1))
2369                        .overflow_hidden()
2370                        .relative()
2371                        .bg(cx.theme().colors().panel_background)
2372                        .px_4()
2373                        .py_3()
2374                        .child(
2375                            div()
2376                                .absolute()
2377                                .top_0()
2378                                .right(px(-1.0))
2379                                .w(px(441.))
2380                                .h(px(167.))
2381                                .child(
2382                                    Vector::new(
2383                                        VectorName::Grid,
2384                                        rems_from_px(441.),
2385                                        rems_from_px(167.),
2386                                    )
2387                                    .color(ui::Color::Custom(cx.theme().colors().text.alpha(0.1))),
2388                                ),
2389                        )
2390                        .child(
2391                            div()
2392                                .absolute()
2393                                .top(px(-8.0))
2394                                .right_0()
2395                                .w(px(400.))
2396                                .h(px(92.))
2397                                .child(
2398                                    Vector::new(
2399                                        VectorName::AiGrid,
2400                                        rems_from_px(400.),
2401                                        rems_from_px(92.),
2402                                    )
2403                                    .color(ui::Color::Custom(cx.theme().colors().text.alpha(0.32))),
2404                                ),
2405                        )
2406                        // .child(
2407                        //     div()
2408                        //         .absolute()
2409                        //         .top_0()
2410                        //         .right(px(360.))
2411                        //         .size(px(401.))
2412                        //         .overflow_hidden()
2413                        //         .bg(cx.theme().colors().panel_background)
2414                        // )
2415                        .child(
2416                            div()
2417                                .absolute()
2418                                .top_0()
2419                                .right_0()
2420                                .w(px(660.))
2421                                .h(px(401.))
2422                                .overflow_hidden()
2423                                .bg(linear_gradient(
2424                                    75.,
2425                                    linear_color_stop(
2426                                        cx.theme().colors().panel_background.alpha(0.01),
2427                                        1.0,
2428                                    ),
2429                                    linear_color_stop(cx.theme().colors().panel_background, 0.45),
2430                                )),
2431                        )
2432                        .child(content),
2433                ),
2434        )
2435    }
2436
2437    fn render_thread_empty_state(
2438        &self,
2439        window: &mut Window,
2440        cx: &mut Context<Self>,
2441    ) -> impl IntoElement {
2442        let recent_history = self
2443            .history_store
2444            .update(cx, |this, cx| this.recent_entries(6, cx));
2445
2446        let model_registry = LanguageModelRegistry::read_global(cx);
2447        let configuration_error =
2448            model_registry.configuration_error(model_registry.default_model(), cx);
2449        let no_error = configuration_error.is_none();
2450        let focus_handle = self.focus_handle(cx);
2451
2452        v_flex()
2453            .size_full()
2454            .bg(cx.theme().colors().panel_background)
2455            .when(recent_history.is_empty(), |this| {
2456                let configuration_error_ref = &configuration_error;
2457                this.child(
2458                    v_flex()
2459                        .size_full()
2460                        .max_w_80()
2461                        .mx_auto()
2462                        .justify_center()
2463                        .items_center()
2464                        .gap_1()
2465                        .child(h_flex().child(Headline::new("Welcome to the Agent Panel")))
2466                        .when(no_error, |parent| {
2467                            parent
2468                                .child(
2469                                    h_flex().child(
2470                                        Label::new("Ask and build anything.")
2471                                            .color(Color::Muted)
2472                                            .mb_2p5(),
2473                                    ),
2474                                )
2475                                .child(
2476                                    Button::new("new-thread", "Start New Thread")
2477                                        .icon(IconName::Plus)
2478                                        .icon_position(IconPosition::Start)
2479                                        .icon_size(IconSize::Small)
2480                                        .icon_color(Color::Muted)
2481                                        .full_width()
2482                                        .key_binding(KeyBinding::for_action_in(
2483                                            &NewThread::default(),
2484                                            &focus_handle,
2485                                            window,
2486                                            cx,
2487                                        ))
2488                                        .on_click(|_event, window, cx| {
2489                                            window.dispatch_action(
2490                                                NewThread::default().boxed_clone(),
2491                                                cx,
2492                                            )
2493                                        }),
2494                                )
2495                                .child(
2496                                    Button::new("context", "Add Context")
2497                                        .icon(IconName::FileCode)
2498                                        .icon_position(IconPosition::Start)
2499                                        .icon_size(IconSize::Small)
2500                                        .icon_color(Color::Muted)
2501                                        .full_width()
2502                                        .key_binding(KeyBinding::for_action_in(
2503                                            &ToggleContextPicker,
2504                                            &focus_handle,
2505                                            window,
2506                                            cx,
2507                                        ))
2508                                        .on_click(|_event, window, cx| {
2509                                            window.dispatch_action(
2510                                                ToggleContextPicker.boxed_clone(),
2511                                                cx,
2512                                            )
2513                                        }),
2514                                )
2515                                .child(
2516                                    Button::new("mode", "Switch Model")
2517                                        .icon(IconName::DatabaseZap)
2518                                        .icon_position(IconPosition::Start)
2519                                        .icon_size(IconSize::Small)
2520                                        .icon_color(Color::Muted)
2521                                        .full_width()
2522                                        .key_binding(KeyBinding::for_action_in(
2523                                            &ToggleModelSelector,
2524                                            &focus_handle,
2525                                            window,
2526                                            cx,
2527                                        ))
2528                                        .on_click(|_event, window, cx| {
2529                                            window.dispatch_action(
2530                                                ToggleModelSelector.boxed_clone(),
2531                                                cx,
2532                                            )
2533                                        }),
2534                                )
2535                                .child(
2536                                    Button::new("settings", "View Settings")
2537                                        .icon(IconName::Settings)
2538                                        .icon_position(IconPosition::Start)
2539                                        .icon_size(IconSize::Small)
2540                                        .icon_color(Color::Muted)
2541                                        .full_width()
2542                                        .key_binding(KeyBinding::for_action_in(
2543                                            &OpenConfiguration,
2544                                            &focus_handle,
2545                                            window,
2546                                            cx,
2547                                        ))
2548                                        .on_click(|_event, window, cx| {
2549                                            window.dispatch_action(
2550                                                OpenConfiguration.boxed_clone(),
2551                                                cx,
2552                                            )
2553                                        }),
2554                                )
2555                        })
2556                        .map(|parent| match configuration_error_ref {
2557                            Some(
2558                                err @ (ConfigurationError::ModelNotFound
2559                                | ConfigurationError::ProviderNotAuthenticated(_)
2560                                | ConfigurationError::NoProvider),
2561                            ) => parent
2562                                .child(h_flex().child(
2563                                    Label::new(err.to_string()).color(Color::Muted).mb_2p5(),
2564                                ))
2565                                .child(
2566                                    Button::new("settings", "Configure a Provider")
2567                                        .icon(IconName::Settings)
2568                                        .icon_position(IconPosition::Start)
2569                                        .icon_size(IconSize::Small)
2570                                        .icon_color(Color::Muted)
2571                                        .full_width()
2572                                        .key_binding(KeyBinding::for_action_in(
2573                                            &OpenConfiguration,
2574                                            &focus_handle,
2575                                            window,
2576                                            cx,
2577                                        ))
2578                                        .on_click(|_event, window, cx| {
2579                                            window.dispatch_action(
2580                                                OpenConfiguration.boxed_clone(),
2581                                                cx,
2582                                            )
2583                                        }),
2584                                ),
2585                            Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
2586                                parent.children(provider.render_accept_terms(
2587                                    LanguageModelProviderTosView::ThreadFreshStart,
2588                                    cx,
2589                                ))
2590                            }
2591                            None => parent,
2592                        }),
2593                )
2594            })
2595            .when(!recent_history.is_empty(), |parent| {
2596                let focus_handle = focus_handle.clone();
2597                let configuration_error_ref = &configuration_error;
2598
2599                parent
2600                    .overflow_hidden()
2601                    .p_1p5()
2602                    .justify_end()
2603                    .gap_1()
2604                    .child(
2605                        h_flex()
2606                            .pl_1p5()
2607                            .pb_1()
2608                            .w_full()
2609                            .justify_between()
2610                            .border_b_1()
2611                            .border_color(cx.theme().colors().border_variant)
2612                            .child(
2613                                Label::new("Recent")
2614                                    .size(LabelSize::Small)
2615                                    .color(Color::Muted),
2616                            )
2617                            .child(
2618                                Button::new("view-history", "View All")
2619                                    .style(ButtonStyle::Subtle)
2620                                    .label_size(LabelSize::Small)
2621                                    .key_binding(
2622                                        KeyBinding::for_action_in(
2623                                            &OpenHistory,
2624                                            &self.focus_handle(cx),
2625                                            window,
2626                                            cx,
2627                                        )
2628                                        .map(|kb| kb.size(rems_from_px(12.))),
2629                                    )
2630                                    .on_click(move |_event, window, cx| {
2631                                        window.dispatch_action(OpenHistory.boxed_clone(), cx);
2632                                    }),
2633                            ),
2634                    )
2635                    .child(
2636                        v_flex()
2637                            .gap_1()
2638                            .children(recent_history.into_iter().enumerate().map(
2639                                |(index, entry)| {
2640                                    // TODO: Add keyboard navigation.
2641                                    let is_hovered =
2642                                        self.hovered_recent_history_item == Some(index);
2643                                    HistoryEntryElement::new(entry.clone(), cx.entity().downgrade())
2644                                        .hovered(is_hovered)
2645                                        .on_hover(cx.listener(
2646                                            move |this, is_hovered, _window, cx| {
2647                                                if *is_hovered {
2648                                                    this.hovered_recent_history_item = Some(index);
2649                                                } else if this.hovered_recent_history_item
2650                                                    == Some(index)
2651                                                {
2652                                                    this.hovered_recent_history_item = None;
2653                                                }
2654                                                cx.notify();
2655                                            },
2656                                        ))
2657                                        .into_any_element()
2658                                },
2659                            )),
2660                    )
2661                    .map(|parent| match configuration_error_ref {
2662                        Some(
2663                            err @ (ConfigurationError::ModelNotFound
2664                            | ConfigurationError::ProviderNotAuthenticated(_)
2665                            | ConfigurationError::NoProvider),
2666                        ) => parent.child(
2667                            Banner::new()
2668                                .severity(ui::Severity::Warning)
2669                                .child(Label::new(err.to_string()).size(LabelSize::Small))
2670                                .action_slot(
2671                                    Button::new("settings", "Configure Provider")
2672                                        .style(ButtonStyle::Tinted(ui::TintColor::Warning))
2673                                        .label_size(LabelSize::Small)
2674                                        .key_binding(
2675                                            KeyBinding::for_action_in(
2676                                                &OpenConfiguration,
2677                                                &focus_handle,
2678                                                window,
2679                                                cx,
2680                                            )
2681                                            .map(|kb| kb.size(rems_from_px(12.))),
2682                                        )
2683                                        .on_click(|_event, window, cx| {
2684                                            window.dispatch_action(
2685                                                OpenConfiguration.boxed_clone(),
2686                                                cx,
2687                                            )
2688                                        }),
2689                                ),
2690                        ),
2691                        Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
2692                            parent.child(Banner::new().severity(ui::Severity::Warning).child(
2693                                h_flex().w_full().children(provider.render_accept_terms(
2694                                    LanguageModelProviderTosView::ThreadEmptyState,
2695                                    cx,
2696                                )),
2697                            ))
2698                        }
2699                        None => parent,
2700                    })
2701            })
2702    }
2703
2704    fn render_tool_use_limit_reached(
2705        &self,
2706        window: &mut Window,
2707        cx: &mut Context<Self>,
2708    ) -> Option<AnyElement> {
2709        let active_thread = match &self.active_view {
2710            ActiveView::Thread { thread, .. } => thread,
2711            ActiveView::AcpThread { .. } => {
2712                return None;
2713            }
2714            ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {
2715                return None;
2716            }
2717        };
2718
2719        let thread = active_thread.read(cx).thread().read(cx);
2720
2721        let tool_use_limit_reached = thread.tool_use_limit_reached();
2722        if !tool_use_limit_reached {
2723            return None;
2724        }
2725
2726        let model = thread.configured_model()?.model;
2727
2728        let focus_handle = self.focus_handle(cx);
2729
2730        let banner = Banner::new()
2731            .severity(ui::Severity::Info)
2732            .child(Label::new("Consecutive tool use limit reached.").size(LabelSize::Small))
2733            .action_slot(
2734                h_flex()
2735                    .gap_1()
2736                    .child(
2737                        Button::new("continue-conversation", "Continue")
2738                            .layer(ElevationIndex::ModalSurface)
2739                            .label_size(LabelSize::Small)
2740                            .key_binding(
2741                                KeyBinding::for_action_in(
2742                                    &ContinueThread,
2743                                    &focus_handle,
2744                                    window,
2745                                    cx,
2746                                )
2747                                .map(|kb| kb.size(rems_from_px(10.))),
2748                            )
2749                            .on_click(cx.listener(|this, _, window, cx| {
2750                                this.continue_conversation(window, cx);
2751                            })),
2752                    )
2753                    .when(model.supports_burn_mode(), |this| {
2754                        this.child(
2755                            Button::new("continue-burn-mode", "Continue with Burn Mode")
2756                                .style(ButtonStyle::Filled)
2757                                .style(ButtonStyle::Tinted(ui::TintColor::Accent))
2758                                .layer(ElevationIndex::ModalSurface)
2759                                .label_size(LabelSize::Small)
2760                                .key_binding(
2761                                    KeyBinding::for_action_in(
2762                                        &ContinueWithBurnMode,
2763                                        &focus_handle,
2764                                        window,
2765                                        cx,
2766                                    )
2767                                    .map(|kb| kb.size(rems_from_px(10.))),
2768                                )
2769                                .tooltip(Tooltip::text("Enable Burn Mode for unlimited tool use."))
2770                                .on_click({
2771                                    let active_thread = active_thread.clone();
2772                                    cx.listener(move |this, _, window, cx| {
2773                                        active_thread.update(cx, |active_thread, cx| {
2774                                            active_thread.thread().update(cx, |thread, _cx| {
2775                                                thread.set_completion_mode(CompletionMode::Burn);
2776                                            });
2777                                        });
2778                                        this.continue_conversation(window, cx);
2779                                    })
2780                                }),
2781                        )
2782                    }),
2783            );
2784
2785        Some(div().px_2().pb_2().child(banner).into_any_element())
2786    }
2787
2788    fn create_copy_button(&self, message: impl Into<String>) -> impl IntoElement {
2789        let message = message.into();
2790
2791        IconButton::new("copy", IconName::Copy)
2792            .icon_size(IconSize::Small)
2793            .icon_color(Color::Muted)
2794            .tooltip(Tooltip::text("Copy Error Message"))
2795            .on_click(move |_, _, cx| {
2796                cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
2797            })
2798    }
2799
2800    fn dismiss_error_button(
2801        &self,
2802        thread: &Entity<ActiveThread>,
2803        cx: &mut Context<Self>,
2804    ) -> impl IntoElement {
2805        IconButton::new("dismiss", IconName::Close)
2806            .icon_size(IconSize::Small)
2807            .icon_color(Color::Muted)
2808            .tooltip(Tooltip::text("Dismiss Error"))
2809            .on_click(cx.listener({
2810                let thread = thread.clone();
2811                move |_, _, _, cx| {
2812                    thread.update(cx, |this, _cx| {
2813                        this.clear_last_error();
2814                    });
2815
2816                    cx.notify();
2817                }
2818            }))
2819    }
2820
2821    fn upgrade_button(
2822        &self,
2823        thread: &Entity<ActiveThread>,
2824        cx: &mut Context<Self>,
2825    ) -> impl IntoElement {
2826        Button::new("upgrade", "Upgrade")
2827            .label_size(LabelSize::Small)
2828            .style(ButtonStyle::Tinted(ui::TintColor::Accent))
2829            .on_click(cx.listener({
2830                let thread = thread.clone();
2831                move |_, _, _, cx| {
2832                    thread.update(cx, |this, _cx| {
2833                        this.clear_last_error();
2834                    });
2835
2836                    cx.open_url(&zed_urls::account_url(cx));
2837                    cx.notify();
2838                }
2839            }))
2840    }
2841
2842    fn error_callout_bg(&self, cx: &Context<Self>) -> Hsla {
2843        cx.theme().status().error.opacity(0.08)
2844    }
2845
2846    fn render_payment_required_error(
2847        &self,
2848        thread: &Entity<ActiveThread>,
2849        cx: &mut Context<Self>,
2850    ) -> AnyElement {
2851        const ERROR_MESSAGE: &str =
2852            "You reached your free usage limit. Upgrade to Zed Pro for more prompts.";
2853
2854        let icon = Icon::new(IconName::XCircle)
2855            .size(IconSize::Small)
2856            .color(Color::Error);
2857
2858        div()
2859            .border_t_1()
2860            .border_color(cx.theme().colors().border)
2861            .child(
2862                Callout::new()
2863                    .icon(icon)
2864                    .title("Free Usage Exceeded")
2865                    .description(ERROR_MESSAGE)
2866                    .tertiary_action(self.upgrade_button(thread, cx))
2867                    .secondary_action(self.create_copy_button(ERROR_MESSAGE))
2868                    .primary_action(self.dismiss_error_button(thread, cx))
2869                    .bg_color(self.error_callout_bg(cx)),
2870            )
2871            .into_any_element()
2872    }
2873
2874    fn render_model_request_limit_reached_error(
2875        &self,
2876        plan: Plan,
2877        thread: &Entity<ActiveThread>,
2878        cx: &mut Context<Self>,
2879    ) -> AnyElement {
2880        let error_message = match plan {
2881            Plan::ZedPro => "Upgrade to usage-based billing for more prompts.",
2882            Plan::ZedProTrial | Plan::Free => "Upgrade to Zed Pro for more prompts.",
2883        };
2884
2885        let icon = Icon::new(IconName::XCircle)
2886            .size(IconSize::Small)
2887            .color(Color::Error);
2888
2889        div()
2890            .border_t_1()
2891            .border_color(cx.theme().colors().border)
2892            .child(
2893                Callout::new()
2894                    .icon(icon)
2895                    .title("Model Prompt Limit Reached")
2896                    .description(error_message)
2897                    .tertiary_action(self.upgrade_button(thread, cx))
2898                    .secondary_action(self.create_copy_button(error_message))
2899                    .primary_action(self.dismiss_error_button(thread, cx))
2900                    .bg_color(self.error_callout_bg(cx)),
2901            )
2902            .into_any_element()
2903    }
2904
2905    fn render_error_message(
2906        &self,
2907        header: SharedString,
2908        message: SharedString,
2909        thread: &Entity<ActiveThread>,
2910        cx: &mut Context<Self>,
2911    ) -> AnyElement {
2912        let message_with_header = format!("{}\n{}", header, message);
2913
2914        let icon = Icon::new(IconName::XCircle)
2915            .size(IconSize::Small)
2916            .color(Color::Error);
2917
2918        div()
2919            .border_t_1()
2920            .border_color(cx.theme().colors().border)
2921            .child(
2922                Callout::new()
2923                    .icon(icon)
2924                    .title(header)
2925                    .description(message.clone())
2926                    .primary_action(self.dismiss_error_button(thread, cx))
2927                    .secondary_action(self.create_copy_button(message_with_header))
2928                    .bg_color(self.error_callout_bg(cx)),
2929            )
2930            .into_any_element()
2931    }
2932
2933    fn render_prompt_editor(
2934        &self,
2935        context_editor: &Entity<TextThreadEditor>,
2936        buffer_search_bar: &Entity<BufferSearchBar>,
2937        window: &mut Window,
2938        cx: &mut Context<Self>,
2939    ) -> Div {
2940        let mut registrar = buffer_search::DivRegistrar::new(
2941            |this, _, _cx| match &this.active_view {
2942                ActiveView::TextThread {
2943                    buffer_search_bar, ..
2944                } => Some(buffer_search_bar.clone()),
2945                _ => None,
2946            },
2947            cx,
2948        );
2949        BufferSearchBar::register(&mut registrar);
2950        registrar
2951            .into_div()
2952            .size_full()
2953            .relative()
2954            .map(|parent| {
2955                buffer_search_bar.update(cx, |buffer_search_bar, cx| {
2956                    if buffer_search_bar.is_dismissed() {
2957                        return parent;
2958                    }
2959                    parent.child(
2960                        div()
2961                            .p(DynamicSpacing::Base08.rems(cx))
2962                            .border_b_1()
2963                            .border_color(cx.theme().colors().border_variant)
2964                            .bg(cx.theme().colors().editor_background)
2965                            .child(buffer_search_bar.render(window, cx)),
2966                    )
2967                })
2968            })
2969            .child(context_editor.clone())
2970            .child(self.render_drag_target(cx))
2971    }
2972
2973    fn render_drag_target(&self, cx: &Context<Self>) -> Div {
2974        let is_local = self.project.read(cx).is_local();
2975        div()
2976            .invisible()
2977            .absolute()
2978            .top_0()
2979            .right_0()
2980            .bottom_0()
2981            .left_0()
2982            .bg(cx.theme().colors().drop_target_background)
2983            .drag_over::<DraggedTab>(|this, _, _, _| this.visible())
2984            .drag_over::<DraggedSelection>(|this, _, _, _| this.visible())
2985            .when(is_local, |this| {
2986                this.drag_over::<ExternalPaths>(|this, _, _, _| this.visible())
2987            })
2988            .on_drop(cx.listener(move |this, tab: &DraggedTab, window, cx| {
2989                let item = tab.pane.read(cx).item_for_index(tab.ix);
2990                let project_paths = item
2991                    .and_then(|item| item.project_path(cx))
2992                    .into_iter()
2993                    .collect::<Vec<_>>();
2994                this.handle_drop(project_paths, vec![], window, cx);
2995            }))
2996            .on_drop(
2997                cx.listener(move |this, selection: &DraggedSelection, window, cx| {
2998                    let project_paths = selection
2999                        .items()
3000                        .filter_map(|item| this.project.read(cx).path_for_entry(item.entry_id, cx))
3001                        .collect::<Vec<_>>();
3002                    this.handle_drop(project_paths, vec![], window, cx);
3003                }),
3004            )
3005            .on_drop(cx.listener(move |this, paths: &ExternalPaths, window, cx| {
3006                let tasks = paths
3007                    .paths()
3008                    .into_iter()
3009                    .map(|path| {
3010                        Workspace::project_path_for_path(this.project.clone(), &path, false, cx)
3011                    })
3012                    .collect::<Vec<_>>();
3013                cx.spawn_in(window, async move |this, cx| {
3014                    let mut paths = vec![];
3015                    let mut added_worktrees = vec![];
3016                    let opened_paths = futures::future::join_all(tasks).await;
3017                    for entry in opened_paths {
3018                        if let Some((worktree, project_path)) = entry.log_err() {
3019                            added_worktrees.push(worktree);
3020                            paths.push(project_path);
3021                        }
3022                    }
3023                    this.update_in(cx, |this, window, cx| {
3024                        this.handle_drop(paths, added_worktrees, window, cx);
3025                    })
3026                    .ok();
3027                })
3028                .detach();
3029            }))
3030    }
3031
3032    fn handle_drop(
3033        &mut self,
3034        paths: Vec<ProjectPath>,
3035        added_worktrees: Vec<Entity<Worktree>>,
3036        window: &mut Window,
3037        cx: &mut Context<Self>,
3038    ) {
3039        match &self.active_view {
3040            ActiveView::Thread { thread, .. } => {
3041                let context_store = thread.read(cx).context_store().clone();
3042                context_store.update(cx, move |context_store, cx| {
3043                    let mut tasks = Vec::new();
3044                    for project_path in &paths {
3045                        tasks.push(context_store.add_file_from_path(
3046                            project_path.clone(),
3047                            false,
3048                            cx,
3049                        ));
3050                    }
3051                    cx.background_spawn(async move {
3052                        futures::future::join_all(tasks).await;
3053                        // Need to hold onto the worktrees until they have already been used when
3054                        // opening the buffers.
3055                        drop(added_worktrees);
3056                    })
3057                    .detach();
3058                });
3059            }
3060            ActiveView::AcpThread { .. } => {
3061                unimplemented!()
3062            }
3063            ActiveView::TextThread { context_editor, .. } => {
3064                context_editor.update(cx, |context_editor, cx| {
3065                    TextThreadEditor::insert_dragged_files(
3066                        context_editor,
3067                        paths,
3068                        added_worktrees,
3069                        window,
3070                        cx,
3071                    );
3072                });
3073            }
3074            ActiveView::History | ActiveView::Configuration => {}
3075        }
3076    }
3077
3078    fn key_context(&self) -> KeyContext {
3079        let mut key_context = KeyContext::new_with_defaults();
3080        key_context.add("AgentPanel");
3081        match &self.active_view {
3082            ActiveView::AcpThread { .. } => key_context.add("acp_thread"),
3083            ActiveView::TextThread { .. } => key_context.add("prompt_editor"),
3084            ActiveView::Thread { .. } | ActiveView::History | ActiveView::Configuration => {}
3085        }
3086        key_context
3087    }
3088}
3089
3090impl Render for AgentPanel {
3091    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3092        // WARNING: Changes to this element hierarchy can have
3093        // non-obvious implications to the layout of children.
3094        //
3095        // If you need to change it, please confirm:
3096        // - The message editor expands (cmd-option-esc) correctly
3097        // - When expanded, the buttons at the bottom of the panel are displayed correctly
3098        // - Font size works as expected and can be changed with cmd-+/cmd-
3099        // - Scrolling in all views works as expected
3100        // - Files can be dropped into the panel
3101        let content = v_flex()
3102            .key_context(self.key_context())
3103            .justify_between()
3104            .size_full()
3105            .on_action(cx.listener(Self::cancel))
3106            .on_action(cx.listener(|this, action: &NewThread, window, cx| {
3107                this.new_thread(action, window, cx);
3108            }))
3109            .on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
3110                this.open_history(window, cx);
3111            }))
3112            .on_action(cx.listener(|this, _: &OpenConfiguration, window, cx| {
3113                this.open_configuration(window, cx);
3114            }))
3115            .on_action(cx.listener(Self::open_active_thread_as_markdown))
3116            .on_action(cx.listener(Self::deploy_rules_library))
3117            .on_action(cx.listener(Self::open_agent_diff))
3118            .on_action(cx.listener(Self::go_back))
3119            .on_action(cx.listener(Self::toggle_navigation_menu))
3120            .on_action(cx.listener(Self::toggle_options_menu))
3121            .on_action(cx.listener(Self::increase_font_size))
3122            .on_action(cx.listener(Self::decrease_font_size))
3123            .on_action(cx.listener(Self::reset_font_size))
3124            .on_action(cx.listener(Self::toggle_zoom))
3125            .on_action(cx.listener(|this, _: &ContinueThread, window, cx| {
3126                this.continue_conversation(window, cx);
3127            }))
3128            .on_action(cx.listener(|this, _: &ContinueWithBurnMode, window, cx| {
3129                match &this.active_view {
3130                    ActiveView::Thread { thread, .. } => {
3131                        thread.update(cx, |active_thread, cx| {
3132                            active_thread.thread().update(cx, |thread, _cx| {
3133                                thread.set_completion_mode(CompletionMode::Burn);
3134                            });
3135                        });
3136                        this.continue_conversation(window, cx);
3137                    }
3138                    ActiveView::AcpThread { .. } => {}
3139                    ActiveView::TextThread { .. }
3140                    | ActiveView::History
3141                    | ActiveView::Configuration => {}
3142                }
3143            }))
3144            .on_action(cx.listener(Self::toggle_burn_mode))
3145            .child(self.render_toolbar(window, cx))
3146            .children(self.render_upsell(window, cx))
3147            .children(self.render_trial_end_upsell(window, cx))
3148            .map(|parent| match &self.active_view {
3149                ActiveView::Thread {
3150                    thread,
3151                    message_editor,
3152                    ..
3153                } => parent
3154                    .relative()
3155                    .child(if thread.read(cx).is_empty() {
3156                        self.render_thread_empty_state(window, cx)
3157                            .into_any_element()
3158                    } else {
3159                        thread.clone().into_any_element()
3160                    })
3161                    .children(self.render_tool_use_limit_reached(window, cx))
3162                    .when_some(thread.read(cx).last_error(), |this, last_error| {
3163                        this.child(
3164                            div()
3165                                .child(match last_error {
3166                                    ThreadError::PaymentRequired => {
3167                                        self.render_payment_required_error(thread, cx)
3168                                    }
3169                                    ThreadError::ModelRequestLimitReached { plan } => self
3170                                        .render_model_request_limit_reached_error(plan, thread, cx),
3171                                    ThreadError::Message { header, message } => {
3172                                        self.render_error_message(header, message, thread, cx)
3173                                    }
3174                                })
3175                                .into_any(),
3176                        )
3177                    })
3178                    .child(h_flex().child(message_editor.clone()))
3179                    .child(self.render_drag_target(cx)),
3180                ActiveView::AcpThread { thread_view, .. } => parent
3181                    .relative()
3182                    .child(thread_view.clone())
3183                    .child(self.render_drag_target(cx)),
3184                ActiveView::History => parent.child(self.history.clone()),
3185                ActiveView::TextThread {
3186                    context_editor,
3187                    buffer_search_bar,
3188                    ..
3189                } => parent.child(self.render_prompt_editor(
3190                    context_editor,
3191                    buffer_search_bar,
3192                    window,
3193                    cx,
3194                )),
3195                ActiveView::Configuration => parent.children(self.configuration.clone()),
3196            });
3197
3198        match self.active_view.which_font_size_used() {
3199            WhichFontSize::AgentFont => {
3200                WithRemSize::new(ThemeSettings::get_global(cx).agent_font_size(cx))
3201                    .size_full()
3202                    .child(content)
3203                    .into_any()
3204            }
3205            _ => content.into_any(),
3206        }
3207    }
3208}
3209
3210struct PromptLibraryInlineAssist {
3211    workspace: WeakEntity<Workspace>,
3212}
3213
3214impl PromptLibraryInlineAssist {
3215    pub fn new(workspace: WeakEntity<Workspace>) -> Self {
3216        Self { workspace }
3217    }
3218}
3219
3220impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
3221    fn assist(
3222        &self,
3223        prompt_editor: &Entity<Editor>,
3224        initial_prompt: Option<String>,
3225        window: &mut Window,
3226        cx: &mut Context<RulesLibrary>,
3227    ) {
3228        InlineAssistant::update_global(cx, |assistant, cx| {
3229            let Some(project) = self
3230                .workspace
3231                .upgrade()
3232                .map(|workspace| workspace.read(cx).project().downgrade())
3233            else {
3234                return;
3235            };
3236            let prompt_store = None;
3237            let thread_store = None;
3238            let text_thread_store = None;
3239            let context_store = cx.new(|_| ContextStore::new(project.clone(), None));
3240            assistant.assist(
3241                &prompt_editor,
3242                self.workspace.clone(),
3243                context_store,
3244                project,
3245                prompt_store,
3246                thread_store,
3247                text_thread_store,
3248                initial_prompt,
3249                window,
3250                cx,
3251            )
3252        })
3253    }
3254
3255    fn focus_agent_panel(
3256        &self,
3257        workspace: &mut Workspace,
3258        window: &mut Window,
3259        cx: &mut Context<Workspace>,
3260    ) -> bool {
3261        workspace.focus_panel::<AgentPanel>(window, cx).is_some()
3262    }
3263}
3264
3265pub struct ConcreteAssistantPanelDelegate;
3266
3267impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
3268    fn active_context_editor(
3269        &self,
3270        workspace: &mut Workspace,
3271        _window: &mut Window,
3272        cx: &mut Context<Workspace>,
3273    ) -> Option<Entity<TextThreadEditor>> {
3274        let panel = workspace.panel::<AgentPanel>(cx)?;
3275        panel.read(cx).active_context_editor()
3276    }
3277
3278    fn open_saved_context(
3279        &self,
3280        workspace: &mut Workspace,
3281        path: Arc<Path>,
3282        window: &mut Window,
3283        cx: &mut Context<Workspace>,
3284    ) -> Task<Result<()>> {
3285        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
3286            return Task::ready(Err(anyhow!("Agent panel not found")));
3287        };
3288
3289        panel.update(cx, |panel, cx| {
3290            panel.open_saved_prompt_editor(path, window, cx)
3291        })
3292    }
3293
3294    fn open_remote_context(
3295        &self,
3296        _workspace: &mut Workspace,
3297        _context_id: assistant_context::ContextId,
3298        _window: &mut Window,
3299        _cx: &mut Context<Workspace>,
3300    ) -> Task<Result<Entity<TextThreadEditor>>> {
3301        Task::ready(Err(anyhow!("opening remote context not implemented")))
3302    }
3303
3304    fn quote_selection(
3305        &self,
3306        workspace: &mut Workspace,
3307        selection_ranges: Vec<Range<Anchor>>,
3308        buffer: Entity<MultiBuffer>,
3309        window: &mut Window,
3310        cx: &mut Context<Workspace>,
3311    ) {
3312        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
3313            return;
3314        };
3315
3316        if !panel.focus_handle(cx).contains_focused(window, cx) {
3317            workspace.toggle_panel_focus::<AgentPanel>(window, cx);
3318        }
3319
3320        panel.update(cx, |_, cx| {
3321            // Wait to create a new context until the workspace is no longer
3322            // being updated.
3323            cx.defer_in(window, move |panel, window, cx| {
3324                if let Some(message_editor) = panel.active_message_editor() {
3325                    message_editor.update(cx, |message_editor, cx| {
3326                        message_editor.context_store().update(cx, |store, cx| {
3327                            let buffer = buffer.read(cx);
3328                            let selection_ranges = selection_ranges
3329                                .into_iter()
3330                                .flat_map(|range| {
3331                                    let (start_buffer, start) =
3332                                        buffer.text_anchor_for_position(range.start, cx)?;
3333                                    let (end_buffer, end) =
3334                                        buffer.text_anchor_for_position(range.end, cx)?;
3335                                    if start_buffer != end_buffer {
3336                                        return None;
3337                                    }
3338                                    Some((start_buffer, start..end))
3339                                })
3340                                .collect::<Vec<_>>();
3341
3342                            for (buffer, range) in selection_ranges {
3343                                store.add_selection(buffer, range, cx);
3344                            }
3345                        })
3346                    })
3347                } else if let Some(context_editor) = panel.active_context_editor() {
3348                    let snapshot = buffer.read(cx).snapshot(cx);
3349                    let selection_ranges = selection_ranges
3350                        .into_iter()
3351                        .map(|range| range.to_point(&snapshot))
3352                        .collect::<Vec<_>>();
3353
3354                    context_editor.update(cx, |context_editor, cx| {
3355                        context_editor.quote_ranges(selection_ranges, snapshot, window, cx)
3356                    });
3357                }
3358            });
3359        });
3360    }
3361}
3362
3363struct Upsell;
3364
3365impl Dismissable for Upsell {
3366    const KEY: &'static str = "dismissed-trial-upsell";
3367}
3368
3369struct TrialEndUpsell;
3370
3371impl Dismissable for TrialEndUpsell {
3372    const KEY: &'static str = "dismissed-trial-end-upsell";
3373}