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