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