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