agent_panel.rs

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