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