agent_panel.rs

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