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 agent_settings::{AgentDockPosition, AgentSettings, DefaultView};
  11use anyhow::{Result, anyhow};
  12use assistant_context_editor::{
  13    AgentPanelDelegate, AssistantContext, ConfigurationError, ContextEditor, ContextEvent,
  14    ContextSummary, SlashCommandCompletionProvider, humanize_token_count,
  15    make_lsp_adapter_delegate, render_remaining_tokens,
  16};
  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 = AgentSettings::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::<AgentSettings>(
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 AgentSettings::get_global(cx).dock {
1385        AgentDockPosition::Left => DockPosition::Left,
1386        AgentDockPosition::Bottom => DockPosition::Bottom,
1387        AgentDockPosition::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::<AgentSettings>(self.fs.clone(), cx, move |settings, _| {
1408            let dock = match position {
1409                DockPosition::Left => AgentDockPosition::Left,
1410                DockPosition::Bottom => AgentDockPosition::Bottom,
1411                DockPosition::Right => AgentDockPosition::Right,
1412            };
1413            settings.set_dock(dock);
1414        });
1415    }
1416
1417    fn size(&self, window: &Window, cx: &App) -> Pixels {
1418        let settings = AgentSettings::get_global(cx);
1419        match self.position(window, cx) {
1420            DockPosition::Left | DockPosition::Right => {
1421                self.width.unwrap_or(settings.default_width)
1422            }
1423            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
1424        }
1425    }
1426
1427    fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
1428        match self.position(window, cx) {
1429            DockPosition::Left | DockPosition::Right => self.width = size,
1430            DockPosition::Bottom => self.height = size,
1431        }
1432        self.serialize(cx);
1433        cx.notify();
1434    }
1435
1436    fn set_active(&mut self, _active: bool, _window: &mut Window, _cx: &mut Context<Self>) {}
1437
1438    fn remote_id() -> Option<proto::PanelId> {
1439        Some(proto::PanelId::AssistantPanel)
1440    }
1441
1442    fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
1443        (self.enabled(cx) && AgentSettings::get_global(cx).button).then_some(IconName::ZedAssistant)
1444    }
1445
1446    fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
1447        Some("Agent Panel")
1448    }
1449
1450    fn toggle_action(&self) -> Box<dyn Action> {
1451        Box::new(ToggleFocus)
1452    }
1453
1454    fn activation_priority(&self) -> u32 {
1455        3
1456    }
1457
1458    fn enabled(&self, cx: &App) -> bool {
1459        AgentSettings::get_global(cx).enabled
1460    }
1461
1462    fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
1463        self.zoomed
1464    }
1465
1466    fn set_zoomed(&mut self, zoomed: bool, _window: &mut Window, cx: &mut Context<Self>) {
1467        self.zoomed = zoomed;
1468        cx.notify();
1469    }
1470}
1471
1472impl AgentPanel {
1473    fn render_title_view(&self, _window: &mut Window, cx: &Context<Self>) -> AnyElement {
1474        const LOADING_SUMMARY_PLACEHOLDER: &str = "Loading Summary…";
1475
1476        let content = match &self.active_view {
1477            ActiveView::Thread {
1478                change_title_editor,
1479                ..
1480            } => {
1481                let active_thread = self.thread.read(cx);
1482                let state = if active_thread.is_empty() {
1483                    &ThreadSummary::Pending
1484                } else {
1485                    active_thread.summary(cx)
1486                };
1487
1488                match state {
1489                    ThreadSummary::Pending => Label::new(ThreadSummary::DEFAULT.clone())
1490                        .truncate()
1491                        .into_any_element(),
1492                    ThreadSummary::Generating => Label::new(LOADING_SUMMARY_PLACEHOLDER)
1493                        .truncate()
1494                        .into_any_element(),
1495                    ThreadSummary::Ready(_) => div()
1496                        .w_full()
1497                        .child(change_title_editor.clone())
1498                        .into_any_element(),
1499                    ThreadSummary::Error => h_flex()
1500                        .w_full()
1501                        .child(change_title_editor.clone())
1502                        .child(
1503                            ui::IconButton::new("retry-summary-generation", IconName::RotateCcw)
1504                                .on_click({
1505                                    let active_thread = self.thread.clone();
1506                                    move |_, _window, cx| {
1507                                        active_thread.update(cx, |thread, cx| {
1508                                            thread.regenerate_summary(cx);
1509                                        });
1510                                    }
1511                                })
1512                                .tooltip(move |_window, cx| {
1513                                    cx.new(|_| {
1514                                        Tooltip::new("Failed to generate title")
1515                                            .meta("Click to try again")
1516                                    })
1517                                    .into()
1518                                }),
1519                        )
1520                        .into_any_element(),
1521                }
1522            }
1523            ActiveView::PromptEditor {
1524                title_editor,
1525                context_editor,
1526                ..
1527            } => {
1528                let summary = context_editor.read(cx).context().read(cx).summary();
1529
1530                match summary {
1531                    ContextSummary::Pending => Label::new(ContextSummary::DEFAULT)
1532                        .truncate()
1533                        .into_any_element(),
1534                    ContextSummary::Content(summary) => {
1535                        if summary.done {
1536                            div()
1537                                .w_full()
1538                                .child(title_editor.clone())
1539                                .into_any_element()
1540                        } else {
1541                            Label::new(LOADING_SUMMARY_PLACEHOLDER)
1542                                .truncate()
1543                                .into_any_element()
1544                        }
1545                    }
1546                    ContextSummary::Error => h_flex()
1547                        .w_full()
1548                        .child(title_editor.clone())
1549                        .child(
1550                            ui::IconButton::new("retry-summary-generation", IconName::RotateCcw)
1551                                .on_click({
1552                                    let context_editor = context_editor.clone();
1553                                    move |_, _window, cx| {
1554                                        context_editor.update(cx, |context_editor, cx| {
1555                                            context_editor.regenerate_summary(cx);
1556                                        });
1557                                    }
1558                                })
1559                                .tooltip(move |_window, cx| {
1560                                    cx.new(|_| {
1561                                        Tooltip::new("Failed to generate title")
1562                                            .meta("Click to try again")
1563                                    })
1564                                    .into()
1565                                }),
1566                        )
1567                        .into_any_element(),
1568                }
1569            }
1570            ActiveView::History => Label::new("History").truncate().into_any_element(),
1571            ActiveView::Configuration => Label::new("Settings").truncate().into_any_element(),
1572        };
1573
1574        h_flex()
1575            .key_context("TitleEditor")
1576            .id("TitleEditor")
1577            .flex_grow()
1578            .w_full()
1579            .max_w_full()
1580            .overflow_x_scroll()
1581            .child(content)
1582            .into_any()
1583    }
1584
1585    fn render_toolbar(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1586        let active_thread = self.thread.read(cx);
1587        let user_store = self.user_store.read(cx);
1588        let thread = active_thread.thread().read(cx);
1589        let thread_id = thread.id().clone();
1590        let is_empty = active_thread.is_empty();
1591        let editor_empty = self.message_editor.read(cx).is_editor_fully_empty(cx);
1592        let last_usage = active_thread.thread().read(cx).last_usage().or_else(|| {
1593            maybe!({
1594                let amount = user_store.model_request_usage_amount()?;
1595                let limit = user_store.model_request_usage_limit()?.variant?;
1596
1597                Some(RequestUsage {
1598                    amount: amount as i32,
1599                    limit: match limit {
1600                        proto::usage_limit::Variant::Limited(limited) => {
1601                            zed_llm_client::UsageLimit::Limited(limited.limit as i32)
1602                        }
1603                        proto::usage_limit::Variant::Unlimited(_) => {
1604                            zed_llm_client::UsageLimit::Unlimited
1605                        }
1606                    },
1607                })
1608            })
1609        });
1610
1611        let account_url = zed_urls::account_url(cx);
1612
1613        let show_token_count = match &self.active_view {
1614            ActiveView::Thread { .. } => !is_empty || !editor_empty,
1615            ActiveView::PromptEditor { .. } => true,
1616            _ => false,
1617        };
1618
1619        let focus_handle = self.focus_handle(cx);
1620
1621        let go_back_button = div().child(
1622            IconButton::new("go-back", IconName::ArrowLeft)
1623                .icon_size(IconSize::Small)
1624                .on_click(cx.listener(|this, _, window, cx| {
1625                    this.go_back(&workspace::GoBack, window, cx);
1626                }))
1627                .tooltip({
1628                    let focus_handle = focus_handle.clone();
1629                    move |window, cx| {
1630                        Tooltip::for_action_in(
1631                            "Go Back",
1632                            &workspace::GoBack,
1633                            &focus_handle,
1634                            window,
1635                            cx,
1636                        )
1637                    }
1638                }),
1639        );
1640
1641        let recent_entries_menu = div().child(
1642            PopoverMenu::new("agent-nav-menu")
1643                .trigger_with_tooltip(
1644                    IconButton::new("agent-nav-menu", IconName::MenuAlt)
1645                        .icon_size(IconSize::Small)
1646                        .style(ui::ButtonStyle::Subtle),
1647                    {
1648                        let focus_handle = focus_handle.clone();
1649                        move |window, cx| {
1650                            Tooltip::for_action_in(
1651                                "Toggle Panel Menu",
1652                                &ToggleNavigationMenu,
1653                                &focus_handle,
1654                                window,
1655                                cx,
1656                            )
1657                        }
1658                    },
1659                )
1660                .anchor(Corner::TopLeft)
1661                .with_handle(self.assistant_navigation_menu_handle.clone())
1662                .menu({
1663                    let menu = self.assistant_navigation_menu.clone();
1664                    move |window, cx| {
1665                        if let Some(menu) = menu.as_ref() {
1666                            menu.update(cx, |_, cx| {
1667                                cx.defer_in(window, |menu, window, cx| {
1668                                    menu.rebuild(window, cx);
1669                                });
1670                            })
1671                        }
1672                        menu.clone()
1673                    }
1674                }),
1675        );
1676
1677        let zoom_in_label = if self.is_zoomed(window, cx) {
1678            "Zoom Out"
1679        } else {
1680            "Zoom In"
1681        };
1682
1683        let agent_extra_menu = PopoverMenu::new("agent-options-menu")
1684            .trigger_with_tooltip(
1685                IconButton::new("agent-options-menu", IconName::Ellipsis)
1686                    .icon_size(IconSize::Small),
1687                {
1688                    let focus_handle = focus_handle.clone();
1689                    move |window, cx| {
1690                        Tooltip::for_action_in(
1691                            "Toggle Agent Menu",
1692                            &ToggleOptionsMenu,
1693                            &focus_handle,
1694                            window,
1695                            cx,
1696                        )
1697                    }
1698                },
1699            )
1700            .anchor(Corner::TopRight)
1701            .with_handle(self.assistant_dropdown_menu_handle.clone())
1702            .menu(move |window, cx| {
1703                Some(ContextMenu::build(window, cx, |mut menu, _window, _cx| {
1704                    menu = menu
1705                        .action("New Thread", NewThread::default().boxed_clone())
1706                        .action("New Text Thread", NewTextThread.boxed_clone())
1707                        .when(!is_empty, |menu| {
1708                            menu.action(
1709                                "New From Summary",
1710                                Box::new(NewThread {
1711                                    from_thread_id: Some(thread_id.clone()),
1712                                }),
1713                            )
1714                        })
1715                        .separator();
1716
1717                    menu = menu
1718                        .header("MCP Servers")
1719                        .action(
1720                            "View Server Extensions",
1721                            Box::new(zed_actions::Extensions {
1722                                category_filter: Some(
1723                                    zed_actions::ExtensionCategoryFilter::ContextServers,
1724                                ),
1725                            }),
1726                        )
1727                        .action("Add Custom Server…", Box::new(AddContextServer))
1728                        .separator();
1729
1730                    if let Some(usage) = last_usage {
1731                        menu = menu
1732                            .header_with_link("Prompt Usage", "Manage", account_url.clone())
1733                            .custom_entry(
1734                                move |_window, cx| {
1735                                    let used_percentage = match usage.limit {
1736                                        UsageLimit::Limited(limit) => {
1737                                            Some((usage.amount as f32 / limit as f32) * 100.)
1738                                        }
1739                                        UsageLimit::Unlimited => None,
1740                                    };
1741
1742                                    h_flex()
1743                                        .flex_1()
1744                                        .gap_1p5()
1745                                        .children(used_percentage.map(|percent| {
1746                                            ProgressBar::new("usage", percent, 100., cx)
1747                                        }))
1748                                        .child(
1749                                            Label::new(match usage.limit {
1750                                                UsageLimit::Limited(limit) => {
1751                                                    format!("{} / {limit}", usage.amount)
1752                                                }
1753                                                UsageLimit::Unlimited => {
1754                                                    format!("{} / ∞", usage.amount)
1755                                                }
1756                                            })
1757                                            .size(LabelSize::Small)
1758                                            .color(Color::Muted),
1759                                        )
1760                                        .into_any_element()
1761                                },
1762                                move |_, cx| cx.open_url(&zed_urls::account_url(cx)),
1763                            )
1764                            .separator()
1765                    }
1766
1767                    menu = menu
1768                        .action("Rules…", Box::new(OpenRulesLibrary::default()))
1769                        .action("Settings", Box::new(OpenConfiguration))
1770                        .action(zoom_in_label, Box::new(ToggleZoom));
1771                    menu
1772                }))
1773            });
1774
1775        h_flex()
1776            .id("assistant-toolbar")
1777            .h(Tab::container_height(cx))
1778            .max_w_full()
1779            .flex_none()
1780            .justify_between()
1781            .gap_2()
1782            .bg(cx.theme().colors().tab_bar_background)
1783            .border_b_1()
1784            .border_color(cx.theme().colors().border)
1785            .child(
1786                h_flex()
1787                    .size_full()
1788                    .pl_1()
1789                    .gap_1()
1790                    .child(match &self.active_view {
1791                        ActiveView::History | ActiveView::Configuration => go_back_button,
1792                        _ => recent_entries_menu,
1793                    })
1794                    .child(self.render_title_view(window, cx)),
1795            )
1796            .child(
1797                h_flex()
1798                    .h_full()
1799                    .gap_2()
1800                    .when(show_token_count, |parent| {
1801                        parent.children(self.render_token_count(&thread, cx))
1802                    })
1803                    .child(
1804                        h_flex()
1805                            .h_full()
1806                            .gap(DynamicSpacing::Base02.rems(cx))
1807                            .px(DynamicSpacing::Base08.rems(cx))
1808                            .border_l_1()
1809                            .border_color(cx.theme().colors().border)
1810                            .child(
1811                                IconButton::new("new", IconName::Plus)
1812                                    .icon_size(IconSize::Small)
1813                                    .style(ButtonStyle::Subtle)
1814                                    .tooltip(move |window, cx| {
1815                                        Tooltip::for_action_in(
1816                                            "New Thread",
1817                                            &NewThread::default(),
1818                                            &focus_handle,
1819                                            window,
1820                                            cx,
1821                                        )
1822                                    })
1823                                    .on_click(move |_event, window, cx| {
1824                                        window.dispatch_action(
1825                                            NewThread::default().boxed_clone(),
1826                                            cx,
1827                                        );
1828                                    }),
1829                            )
1830                            .child(agent_extra_menu),
1831                    ),
1832            )
1833    }
1834
1835    fn render_token_count(&self, thread: &Thread, cx: &App) -> Option<AnyElement> {
1836        let is_generating = thread.is_generating();
1837        let message_editor = self.message_editor.read(cx);
1838
1839        let conversation_token_usage = thread.total_token_usage()?;
1840
1841        let (total_token_usage, is_estimating) = if let Some((editing_message_id, unsent_tokens)) =
1842            self.thread.read(cx).editing_message_id()
1843        {
1844            let combined = thread
1845                .token_usage_up_to_message(editing_message_id)
1846                .add(unsent_tokens);
1847
1848            (combined, unsent_tokens > 0)
1849        } else {
1850            let unsent_tokens = message_editor.last_estimated_token_count().unwrap_or(0);
1851            let combined = conversation_token_usage.add(unsent_tokens);
1852
1853            (combined, unsent_tokens > 0)
1854        };
1855
1856        let is_waiting_to_update_token_count = message_editor.is_waiting_to_update_token_count();
1857
1858        match &self.active_view {
1859            ActiveView::Thread { .. } => {
1860                if total_token_usage.total == 0 {
1861                    return None;
1862                }
1863
1864                let token_color = match total_token_usage.ratio() {
1865                    TokenUsageRatio::Normal if is_estimating => Color::Default,
1866                    TokenUsageRatio::Normal => Color::Muted,
1867                    TokenUsageRatio::Warning => Color::Warning,
1868                    TokenUsageRatio::Exceeded => Color::Error,
1869                };
1870
1871                let token_count = h_flex()
1872                    .id("token-count")
1873                    .flex_shrink_0()
1874                    .gap_0p5()
1875                    .when(!is_generating && is_estimating, |parent| {
1876                        parent
1877                            .child(
1878                                h_flex()
1879                                    .mr_1()
1880                                    .size_2p5()
1881                                    .justify_center()
1882                                    .rounded_full()
1883                                    .bg(cx.theme().colors().text.opacity(0.1))
1884                                    .child(
1885                                        div().size_1().rounded_full().bg(cx.theme().colors().text),
1886                                    ),
1887                            )
1888                            .tooltip(move |window, cx| {
1889                                Tooltip::with_meta(
1890                                    "Estimated New Token Count",
1891                                    None,
1892                                    format!(
1893                                        "Current Conversation Tokens: {}",
1894                                        humanize_token_count(conversation_token_usage.total)
1895                                    ),
1896                                    window,
1897                                    cx,
1898                                )
1899                            })
1900                    })
1901                    .child(
1902                        Label::new(humanize_token_count(total_token_usage.total))
1903                            .size(LabelSize::Small)
1904                            .color(token_color)
1905                            .map(|label| {
1906                                if is_generating || is_waiting_to_update_token_count {
1907                                    label
1908                                        .with_animation(
1909                                            "used-tokens-label",
1910                                            Animation::new(Duration::from_secs(2))
1911                                                .repeat()
1912                                                .with_easing(pulsating_between(0.6, 1.)),
1913                                            |label, delta| label.alpha(delta),
1914                                        )
1915                                        .into_any()
1916                                } else {
1917                                    label.into_any_element()
1918                                }
1919                            }),
1920                    )
1921                    .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
1922                    .child(
1923                        Label::new(humanize_token_count(total_token_usage.max))
1924                            .size(LabelSize::Small)
1925                            .color(Color::Muted),
1926                    )
1927                    .into_any();
1928
1929                Some(token_count)
1930            }
1931            ActiveView::PromptEditor { context_editor, .. } => {
1932                let element = render_remaining_tokens(context_editor, cx)?;
1933
1934                Some(element.into_any_element())
1935            }
1936            _ => None,
1937        }
1938    }
1939
1940    fn should_render_trial_end_upsell(&self, cx: &mut Context<Self>) -> bool {
1941        if TrialEndUpsell::dismissed() {
1942            return false;
1943        }
1944
1945        let plan = self.user_store.read(cx).current_plan();
1946        let has_previous_trial = self.user_store.read(cx).trial_started_at().is_some();
1947
1948        matches!(plan, Some(Plan::Free)) && has_previous_trial
1949    }
1950
1951    fn should_render_upsell(&self, cx: &mut Context<Self>) -> bool {
1952        if !matches!(self.active_view, ActiveView::Thread { .. }) {
1953            return false;
1954        }
1955
1956        if self.hide_upsell || Upsell::dismissed() {
1957            return false;
1958        }
1959
1960        let is_using_zed_provider = self
1961            .thread
1962            .read(cx)
1963            .thread()
1964            .read(cx)
1965            .configured_model()
1966            .map_or(false, |model| {
1967                model.provider.id().0 == ZED_CLOUD_PROVIDER_ID
1968            });
1969        if !is_using_zed_provider {
1970            return false;
1971        }
1972
1973        let plan = self.user_store.read(cx).current_plan();
1974        if matches!(plan, Some(Plan::ZedPro | Plan::ZedProTrial)) {
1975            return false;
1976        }
1977
1978        let has_previous_trial = self.user_store.read(cx).trial_started_at().is_some();
1979        if has_previous_trial {
1980            return false;
1981        }
1982
1983        true
1984    }
1985
1986    fn render_upsell(
1987        &self,
1988        _window: &mut Window,
1989        cx: &mut Context<Self>,
1990    ) -> Option<impl IntoElement> {
1991        if !self.should_render_upsell(cx) {
1992            return None;
1993        }
1994
1995        if self.user_store.read(cx).account_too_young() {
1996            Some(self.render_young_account_upsell(cx).into_any_element())
1997        } else {
1998            Some(self.render_trial_upsell(cx).into_any_element())
1999        }
2000    }
2001
2002    fn render_young_account_upsell(&self, cx: &mut Context<Self>) -> impl IntoElement {
2003        let checkbox = CheckboxWithLabel::new(
2004            "dont-show-again",
2005            Label::new("Don't show again").color(Color::Muted),
2006            ToggleState::Unselected,
2007            move |toggle_state, _window, cx| {
2008                let toggle_state_bool = toggle_state.selected();
2009
2010                Upsell::set_dismissed(toggle_state_bool, cx);
2011            },
2012        );
2013
2014        let contents = div()
2015            .size_full()
2016            .gap_2()
2017            .flex()
2018            .flex_col()
2019            .child(Headline::new("Build better with Zed Pro").size(HeadlineSize::Small))
2020            .child(
2021                Label::new("Your GitHub account was created less than 30 days ago, so we can't offer you a free trial.")
2022                    .size(LabelSize::Small),
2023            )
2024            .child(
2025                Label::new(
2026                    "Use your own API keys, upgrade to Zed Pro or send an email to billing-support@zed.dev.",
2027                )
2028                .color(Color::Muted),
2029            )
2030            .child(
2031                h_flex()
2032                    .w_full()
2033                    .px_neg_1()
2034                    .justify_between()
2035                    .items_center()
2036                    .child(h_flex().items_center().gap_1().child(checkbox))
2037                    .child(
2038                        h_flex()
2039                            .gap_2()
2040                            .child(
2041                                Button::new("dismiss-button", "Not Now")
2042                                    .style(ButtonStyle::Transparent)
2043                                    .color(Color::Muted)
2044                                    .on_click({
2045                                        let agent_panel = cx.entity();
2046                                        move |_, _, cx| {
2047                                            agent_panel.update(cx, |this, cx| {
2048                                                this.hide_upsell = true;
2049                                                cx.notify();
2050                                            });
2051                                        }
2052                                    }),
2053                            )
2054                            .child(
2055                                Button::new("cta-button", "Upgrade to Zed Pro")
2056                                    .style(ButtonStyle::Transparent)
2057                                    .on_click(|_, _, cx| cx.open_url(&zed_urls::account_url(cx))),
2058                            ),
2059                    ),
2060            );
2061
2062        self.render_upsell_container(cx, contents)
2063    }
2064
2065    fn render_trial_upsell(&self, cx: &mut Context<Self>) -> impl IntoElement {
2066        let checkbox = CheckboxWithLabel::new(
2067            "dont-show-again",
2068            Label::new("Don't show again").color(Color::Muted),
2069            ToggleState::Unselected,
2070            move |toggle_state, _window, cx| {
2071                let toggle_state_bool = toggle_state.selected();
2072
2073                Upsell::set_dismissed(toggle_state_bool, cx);
2074            },
2075        );
2076
2077        let contents = div()
2078            .size_full()
2079            .gap_2()
2080            .flex()
2081            .flex_col()
2082            .child(Headline::new("Build better with Zed Pro").size(HeadlineSize::Small))
2083            .child(
2084                Label::new("Try Zed Pro for free for 14 days - no credit card required.")
2085                    .size(LabelSize::Small),
2086            )
2087            .child(
2088                Label::new(
2089                    "Use your own API keys or enable usage-based billing once you hit the cap.",
2090                )
2091                .color(Color::Muted),
2092            )
2093            .child(
2094                h_flex()
2095                    .w_full()
2096                    .px_neg_1()
2097                    .justify_between()
2098                    .items_center()
2099                    .child(h_flex().items_center().gap_1().child(checkbox))
2100                    .child(
2101                        h_flex()
2102                            .gap_2()
2103                            .child(
2104                                Button::new("dismiss-button", "Not Now")
2105                                    .style(ButtonStyle::Transparent)
2106                                    .color(Color::Muted)
2107                                    .on_click({
2108                                        let agent_panel = cx.entity();
2109                                        move |_, _, cx| {
2110                                            agent_panel.update(cx, |this, cx| {
2111                                                this.hide_upsell = true;
2112                                                cx.notify();
2113                                            });
2114                                        }
2115                                    }),
2116                            )
2117                            .child(
2118                                Button::new("cta-button", "Start Trial")
2119                                    .style(ButtonStyle::Transparent)
2120                                    .on_click(|_, _, cx| cx.open_url(&zed_urls::account_url(cx))),
2121                            ),
2122                    ),
2123            );
2124
2125        self.render_upsell_container(cx, contents)
2126    }
2127
2128    fn render_trial_end_upsell(
2129        &self,
2130        _window: &mut Window,
2131        cx: &mut Context<Self>,
2132    ) -> Option<impl IntoElement> {
2133        if !self.should_render_trial_end_upsell(cx) {
2134            return None;
2135        }
2136
2137        Some(
2138            self.render_upsell_container(
2139                cx,
2140                div()
2141                    .size_full()
2142                    .gap_2()
2143                    .flex()
2144                    .flex_col()
2145                    .child(
2146                        Headline::new("Your Zed Pro trial has expired.").size(HeadlineSize::Small),
2147                    )
2148                    .child(
2149                        Label::new("You've been automatically reset to the free plan.")
2150                            .size(LabelSize::Small),
2151                    )
2152                    .child(
2153                        h_flex()
2154                            .w_full()
2155                            .px_neg_1()
2156                            .justify_between()
2157                            .items_center()
2158                            .child(div())
2159                            .child(
2160                                h_flex()
2161                                    .gap_2()
2162                                    .child(
2163                                        Button::new("dismiss-button", "Stay on Free")
2164                                            .style(ButtonStyle::Transparent)
2165                                            .color(Color::Muted)
2166                                            .on_click({
2167                                                let agent_panel = cx.entity();
2168                                                move |_, _, cx| {
2169                                                    agent_panel.update(cx, |_this, cx| {
2170                                                        TrialEndUpsell::set_dismissed(true, cx);
2171                                                        cx.notify();
2172                                                    });
2173                                                }
2174                                            }),
2175                                    )
2176                                    .child(
2177                                        Button::new("cta-button", "Upgrade to Zed Pro")
2178                                            .style(ButtonStyle::Transparent)
2179                                            .on_click(|_, _, cx| {
2180                                                cx.open_url(&zed_urls::account_url(cx))
2181                                            }),
2182                                    ),
2183                            ),
2184                    ),
2185            ),
2186        )
2187    }
2188
2189    fn render_upsell_container(&self, cx: &mut Context<Self>, content: Div) -> Div {
2190        div().p_2().child(
2191            v_flex()
2192                .w_full()
2193                .elevation_2(cx)
2194                .rounded(px(8.))
2195                .bg(cx.theme().colors().background.alpha(0.5))
2196                .p(px(3.))
2197                .child(
2198                    div()
2199                        .gap_2()
2200                        .flex()
2201                        .flex_col()
2202                        .size_full()
2203                        .border_1()
2204                        .rounded(px(5.))
2205                        .border_color(cx.theme().colors().text.alpha(0.1))
2206                        .overflow_hidden()
2207                        .relative()
2208                        .bg(cx.theme().colors().panel_background)
2209                        .px_4()
2210                        .py_3()
2211                        .child(
2212                            div()
2213                                .absolute()
2214                                .top_0()
2215                                .right(px(-1.0))
2216                                .w(px(441.))
2217                                .h(px(167.))
2218                                .child(
2219                                    Vector::new(
2220                                        VectorName::Grid,
2221                                        rems_from_px(441.),
2222                                        rems_from_px(167.),
2223                                    )
2224                                    .color(ui::Color::Custom(cx.theme().colors().text.alpha(0.1))),
2225                                ),
2226                        )
2227                        .child(
2228                            div()
2229                                .absolute()
2230                                .top(px(-8.0))
2231                                .right_0()
2232                                .w(px(400.))
2233                                .h(px(92.))
2234                                .child(
2235                                    Vector::new(
2236                                        VectorName::AiGrid,
2237                                        rems_from_px(400.),
2238                                        rems_from_px(92.),
2239                                    )
2240                                    .color(ui::Color::Custom(cx.theme().colors().text.alpha(0.32))),
2241                                ),
2242                        )
2243                        // .child(
2244                        //     div()
2245                        //         .absolute()
2246                        //         .top_0()
2247                        //         .right(px(360.))
2248                        //         .size(px(401.))
2249                        //         .overflow_hidden()
2250                        //         .bg(cx.theme().colors().panel_background)
2251                        // )
2252                        .child(
2253                            div()
2254                                .absolute()
2255                                .top_0()
2256                                .right_0()
2257                                .w(px(660.))
2258                                .h(px(401.))
2259                                .overflow_hidden()
2260                                .bg(linear_gradient(
2261                                    75.,
2262                                    linear_color_stop(
2263                                        cx.theme().colors().panel_background.alpha(0.01),
2264                                        1.0,
2265                                    ),
2266                                    linear_color_stop(cx.theme().colors().panel_background, 0.45),
2267                                )),
2268                        )
2269                        .child(content),
2270                ),
2271        )
2272    }
2273
2274    fn render_active_thread_or_empty_state(
2275        &self,
2276        window: &mut Window,
2277        cx: &mut Context<Self>,
2278    ) -> AnyElement {
2279        if self.thread.read(cx).is_empty() {
2280            return self
2281                .render_thread_empty_state(window, cx)
2282                .into_any_element();
2283        }
2284
2285        self.thread.clone().into_any_element()
2286    }
2287
2288    fn configuration_error(&self, cx: &App) -> Option<ConfigurationError> {
2289        let Some(model) = LanguageModelRegistry::read_global(cx).default_model() else {
2290            return Some(ConfigurationError::NoProvider);
2291        };
2292
2293        if !model.provider.is_authenticated(cx) {
2294            return Some(ConfigurationError::ProviderNotAuthenticated);
2295        }
2296
2297        if model.provider.must_accept_terms(cx) {
2298            return Some(ConfigurationError::ProviderPendingTermsAcceptance(
2299                model.provider,
2300            ));
2301        }
2302
2303        None
2304    }
2305
2306    fn render_thread_empty_state(
2307        &self,
2308        window: &mut Window,
2309        cx: &mut Context<Self>,
2310    ) -> impl IntoElement {
2311        let recent_history = self
2312            .history_store
2313            .update(cx, |this, cx| this.recent_entries(6, cx));
2314
2315        let configuration_error = self.configuration_error(cx);
2316        let no_error = configuration_error.is_none();
2317        let focus_handle = self.focus_handle(cx);
2318
2319        v_flex()
2320            .size_full()
2321            .bg(cx.theme().colors().panel_background)
2322            .when(recent_history.is_empty(), |this| {
2323                let configuration_error_ref = &configuration_error;
2324                this.child(
2325                    v_flex()
2326                        .size_full()
2327                        .max_w_80()
2328                        .mx_auto()
2329                        .justify_center()
2330                        .items_center()
2331                        .gap_1()
2332                        .child(
2333                            h_flex().child(
2334                                Headline::new("Welcome to the Agent Panel")
2335                            ),
2336                        )
2337                        .when(no_error, |parent| {
2338                            parent
2339                                .child(
2340                                    h_flex().child(
2341                                        Label::new("Ask and build anything.")
2342                                            .color(Color::Muted)
2343                                            .mb_2p5(),
2344                                    ),
2345                                )
2346                                .child(
2347                                    Button::new("new-thread", "Start New Thread")
2348                                        .icon(IconName::Plus)
2349                                        .icon_position(IconPosition::Start)
2350                                        .icon_size(IconSize::Small)
2351                                        .icon_color(Color::Muted)
2352                                        .full_width()
2353                                        .key_binding(KeyBinding::for_action_in(
2354                                            &NewThread::default(),
2355                                            &focus_handle,
2356                                            window,
2357                                            cx,
2358                                        ))
2359                                        .on_click(|_event, window, cx| {
2360                                            window.dispatch_action(NewThread::default().boxed_clone(), cx)
2361                                        }),
2362                                )
2363                                .child(
2364                                    Button::new("context", "Add Context")
2365                                        .icon(IconName::FileCode)
2366                                        .icon_position(IconPosition::Start)
2367                                        .icon_size(IconSize::Small)
2368                                        .icon_color(Color::Muted)
2369                                        .full_width()
2370                                        .key_binding(KeyBinding::for_action_in(
2371                                            &ToggleContextPicker,
2372                                            &focus_handle,
2373                                            window,
2374                                            cx,
2375                                        ))
2376                                        .on_click(|_event, window, cx| {
2377                                            window.dispatch_action(ToggleContextPicker.boxed_clone(), cx)
2378                                        }),
2379                                )
2380                                .child(
2381                                    Button::new("mode", "Switch Model")
2382                                        .icon(IconName::DatabaseZap)
2383                                        .icon_position(IconPosition::Start)
2384                                        .icon_size(IconSize::Small)
2385                                        .icon_color(Color::Muted)
2386                                        .full_width()
2387                                        .key_binding(KeyBinding::for_action_in(
2388                                            &ToggleModelSelector,
2389                                            &focus_handle,
2390                                            window,
2391                                            cx,
2392                                        ))
2393                                        .on_click(|_event, window, cx| {
2394                                            window.dispatch_action(ToggleModelSelector.boxed_clone(), cx)
2395                                        }),
2396                                )
2397                                .child(
2398                                    Button::new("settings", "View Settings")
2399                                        .icon(IconName::Settings)
2400                                        .icon_position(IconPosition::Start)
2401                                        .icon_size(IconSize::Small)
2402                                        .icon_color(Color::Muted)
2403                                        .full_width()
2404                                        .key_binding(KeyBinding::for_action_in(
2405                                            &OpenConfiguration,
2406                                            &focus_handle,
2407                                            window,
2408                                            cx,
2409                                        ))
2410                                        .on_click(|_event, window, cx| {
2411                                            window.dispatch_action(OpenConfiguration.boxed_clone(), cx)
2412                                        }),
2413                                )
2414                        })
2415                        .map(|parent| {
2416                            match configuration_error_ref {
2417                                Some(ConfigurationError::ProviderNotAuthenticated)
2418                                | Some(ConfigurationError::NoProvider) => {
2419                                    parent
2420                                        .child(
2421                                            h_flex().child(
2422                                                Label::new("To start using the agent, configure at least one LLM provider.")
2423                                                    .color(Color::Muted)
2424                                                    .mb_2p5()
2425                                            )
2426                                        )
2427                                        .child(
2428                                            Button::new("settings", "Configure a Provider")
2429                                                .icon(IconName::Settings)
2430                                                .icon_position(IconPosition::Start)
2431                                                .icon_size(IconSize::Small)
2432                                                .icon_color(Color::Muted)
2433                                                .full_width()
2434                                                .key_binding(KeyBinding::for_action_in(
2435                                                    &OpenConfiguration,
2436                                                    &focus_handle,
2437                                                    window,
2438                                                    cx,
2439                                                ))
2440                                                .on_click(|_event, window, cx| {
2441                                                    window.dispatch_action(OpenConfiguration.boxed_clone(), cx)
2442                                                }),
2443                                        )
2444                                }
2445                                Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
2446                                    parent.children(
2447                                        provider.render_accept_terms(
2448                                            LanguageModelProviderTosView::ThreadFreshStart,
2449                                            cx,
2450                                        ),
2451                                    )
2452                                }
2453                                None => parent,
2454                            }
2455                        })
2456                )
2457            })
2458            .when(!recent_history.is_empty(), |parent| {
2459                let focus_handle = focus_handle.clone();
2460                let configuration_error_ref = &configuration_error;
2461
2462                parent
2463                    .overflow_hidden()
2464                    .p_1p5()
2465                    .justify_end()
2466                    .gap_1()
2467                    .child(
2468                        h_flex()
2469                            .pl_1p5()
2470                            .pb_1()
2471                            .w_full()
2472                            .justify_between()
2473                            .border_b_1()
2474                            .border_color(cx.theme().colors().border_variant)
2475                            .child(
2476                                Label::new("Recent")
2477                                    .size(LabelSize::Small)
2478                                    .color(Color::Muted),
2479                            )
2480                            .child(
2481                                Button::new("view-history", "View All")
2482                                    .style(ButtonStyle::Subtle)
2483                                    .label_size(LabelSize::Small)
2484                                    .key_binding(
2485                                        KeyBinding::for_action_in(
2486                                            &OpenHistory,
2487                                            &self.focus_handle(cx),
2488                                            window,
2489                                            cx,
2490                                        ).map(|kb| kb.size(rems_from_px(12.))),
2491                                    )
2492                                    .on_click(move |_event, window, cx| {
2493                                        window.dispatch_action(OpenHistory.boxed_clone(), cx);
2494                                    }),
2495                            ),
2496                    )
2497                    .child(
2498                        v_flex()
2499                            .gap_1()
2500                            .children(
2501                                recent_history.into_iter().enumerate().map(|(index, entry)| {
2502                                    // TODO: Add keyboard navigation.
2503                                    let is_hovered = self.hovered_recent_history_item == Some(index);
2504                                    HistoryEntryElement::new(entry.clone(), cx.entity().downgrade())
2505                                        .hovered(is_hovered)
2506                                        .on_hover(cx.listener(move |this, is_hovered, _window, cx| {
2507                                            if *is_hovered {
2508                                                this.hovered_recent_history_item = Some(index);
2509                                            } else if this.hovered_recent_history_item == Some(index) {
2510                                                this.hovered_recent_history_item = None;
2511                                            }
2512                                            cx.notify();
2513                                        }))
2514                                        .into_any_element()
2515                                }),
2516                            )
2517                    )
2518                    .map(|parent| {
2519                        match configuration_error_ref {
2520                            Some(ConfigurationError::ProviderNotAuthenticated)
2521                            | Some(ConfigurationError::NoProvider) => {
2522                                parent
2523                                    .child(
2524                                        Banner::new()
2525                                            .severity(ui::Severity::Warning)
2526                                            .child(
2527                                                Label::new(
2528                                                    "Configure at least one LLM provider to start using the panel.",
2529                                                )
2530                                                .size(LabelSize::Small),
2531                                            )
2532                                            .action_slot(
2533                                                Button::new("settings", "Configure Provider")
2534                                                    .style(ButtonStyle::Tinted(ui::TintColor::Warning))
2535                                                    .label_size(LabelSize::Small)
2536                                                    .key_binding(
2537                                                        KeyBinding::for_action_in(
2538                                                            &OpenConfiguration,
2539                                                            &focus_handle,
2540                                                            window,
2541                                                            cx,
2542                                                        )
2543                                                        .map(|kb| kb.size(rems_from_px(12.))),
2544                                                    )
2545                                                    .on_click(|_event, window, cx| {
2546                                                        window.dispatch_action(
2547                                                            OpenConfiguration.boxed_clone(),
2548                                                            cx,
2549                                                        )
2550                                                    }),
2551                                            ),
2552                                    )
2553                            }
2554                            Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
2555                                parent
2556                                    .child(
2557                                        Banner::new()
2558                                            .severity(ui::Severity::Warning)
2559                                            .child(
2560                                                h_flex()
2561                                                    .w_full()
2562                                                    .children(
2563                                                        provider.render_accept_terms(
2564                                                            LanguageModelProviderTosView::ThreadtEmptyState,
2565                                                            cx,
2566                                                        ),
2567                                                    ),
2568                                            ),
2569                                    )
2570                            }
2571                            None => parent,
2572                        }
2573                    })
2574            })
2575    }
2576
2577    fn render_tool_use_limit_reached(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
2578        let tool_use_limit_reached = self
2579            .thread
2580            .read(cx)
2581            .thread()
2582            .read(cx)
2583            .tool_use_limit_reached();
2584        if !tool_use_limit_reached {
2585            return None;
2586        }
2587
2588        let model = self
2589            .thread
2590            .read(cx)
2591            .thread()
2592            .read(cx)
2593            .configured_model()?
2594            .model;
2595
2596        let max_mode_upsell = if model.supports_max_mode() {
2597            " Enable max mode for unlimited tool use."
2598        } else {
2599            ""
2600        };
2601
2602        let banner = Banner::new()
2603            .severity(ui::Severity::Info)
2604            .child(h_flex().child(Label::new(format!(
2605                "Consecutive tool use limit reached.{max_mode_upsell}"
2606            ))));
2607
2608        Some(div().px_2().pb_2().child(banner).into_any_element())
2609    }
2610
2611    fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
2612        let last_error = self.thread.read(cx).last_error()?;
2613
2614        Some(
2615            div()
2616                .absolute()
2617                .right_3()
2618                .bottom_12()
2619                .max_w_96()
2620                .py_2()
2621                .px_3()
2622                .elevation_2(cx)
2623                .occlude()
2624                .child(match last_error {
2625                    ThreadError::PaymentRequired => self.render_payment_required_error(cx),
2626                    ThreadError::ModelRequestLimitReached { plan } => {
2627                        self.render_model_request_limit_reached_error(plan, cx)
2628                    }
2629                    ThreadError::Message { header, message } => {
2630                        self.render_error_message(header, message, cx)
2631                    }
2632                })
2633                .into_any(),
2634        )
2635    }
2636
2637    fn render_payment_required_error(&self, cx: &mut Context<Self>) -> AnyElement {
2638        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.";
2639
2640        v_flex()
2641            .gap_0p5()
2642            .child(
2643                h_flex()
2644                    .gap_1p5()
2645                    .items_center()
2646                    .child(Icon::new(IconName::XCircle).color(Color::Error))
2647                    .child(Label::new("Free Usage Exceeded").weight(FontWeight::MEDIUM)),
2648            )
2649            .child(
2650                div()
2651                    .id("error-message")
2652                    .max_h_24()
2653                    .overflow_y_scroll()
2654                    .child(Label::new(ERROR_MESSAGE)),
2655            )
2656            .child(
2657                h_flex()
2658                    .justify_end()
2659                    .mt_1()
2660                    .gap_1()
2661                    .child(self.create_copy_button(ERROR_MESSAGE))
2662                    .child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
2663                        |this, _, _, cx| {
2664                            this.thread.update(cx, |this, _cx| {
2665                                this.clear_last_error();
2666                            });
2667
2668                            cx.open_url(&zed_urls::account_url(cx));
2669                            cx.notify();
2670                        },
2671                    )))
2672                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
2673                        |this, _, _, cx| {
2674                            this.thread.update(cx, |this, _cx| {
2675                                this.clear_last_error();
2676                            });
2677
2678                            cx.notify();
2679                        },
2680                    ))),
2681            )
2682            .into_any()
2683    }
2684
2685    fn render_model_request_limit_reached_error(
2686        &self,
2687        plan: Plan,
2688        cx: &mut Context<Self>,
2689    ) -> AnyElement {
2690        let error_message = match plan {
2691            Plan::ZedPro => {
2692                "Model request limit reached. Upgrade to usage-based billing for more requests."
2693            }
2694            Plan::ZedProTrial => {
2695                "Model request limit reached. Upgrade to Zed Pro for more requests."
2696            }
2697            Plan::Free => "Model request limit reached. Upgrade to Zed Pro for more requests.",
2698        };
2699        let call_to_action = match plan {
2700            Plan::ZedPro => "Upgrade to usage-based billing",
2701            Plan::ZedProTrial => "Upgrade to Zed Pro",
2702            Plan::Free => "Upgrade to Zed Pro",
2703        };
2704
2705        v_flex()
2706            .gap_0p5()
2707            .child(
2708                h_flex()
2709                    .gap_1p5()
2710                    .items_center()
2711                    .child(Icon::new(IconName::XCircle).color(Color::Error))
2712                    .child(Label::new("Model Request Limit Reached").weight(FontWeight::MEDIUM)),
2713            )
2714            .child(
2715                div()
2716                    .id("error-message")
2717                    .max_h_24()
2718                    .overflow_y_scroll()
2719                    .child(Label::new(error_message)),
2720            )
2721            .child(
2722                h_flex()
2723                    .justify_end()
2724                    .mt_1()
2725                    .gap_1()
2726                    .child(self.create_copy_button(error_message))
2727                    .child(
2728                        Button::new("subscribe", call_to_action).on_click(cx.listener(
2729                            |this, _, _, cx| {
2730                                this.thread.update(cx, |this, _cx| {
2731                                    this.clear_last_error();
2732                                });
2733
2734                                cx.open_url(&zed_urls::account_url(cx));
2735                                cx.notify();
2736                            },
2737                        )),
2738                    )
2739                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
2740                        |this, _, _, cx| {
2741                            this.thread.update(cx, |this, _cx| {
2742                                this.clear_last_error();
2743                            });
2744
2745                            cx.notify();
2746                        },
2747                    ))),
2748            )
2749            .into_any()
2750    }
2751
2752    fn render_error_message(
2753        &self,
2754        header: SharedString,
2755        message: SharedString,
2756        cx: &mut Context<Self>,
2757    ) -> AnyElement {
2758        let message_with_header = format!("{}\n{}", header, message);
2759        v_flex()
2760            .gap_0p5()
2761            .child(
2762                h_flex()
2763                    .gap_1p5()
2764                    .items_center()
2765                    .child(Icon::new(IconName::XCircle).color(Color::Error))
2766                    .child(Label::new(header).weight(FontWeight::MEDIUM)),
2767            )
2768            .child(
2769                div()
2770                    .id("error-message")
2771                    .max_h_32()
2772                    .overflow_y_scroll()
2773                    .child(Label::new(message.clone())),
2774            )
2775            .child(
2776                h_flex()
2777                    .justify_end()
2778                    .mt_1()
2779                    .gap_1()
2780                    .child(self.create_copy_button(message_with_header))
2781                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
2782                        |this, _, _, cx| {
2783                            this.thread.update(cx, |this, _cx| {
2784                                this.clear_last_error();
2785                            });
2786
2787                            cx.notify();
2788                        },
2789                    ))),
2790            )
2791            .into_any()
2792    }
2793
2794    fn render_prompt_editor(
2795        &self,
2796        context_editor: &Entity<ContextEditor>,
2797        buffer_search_bar: &Entity<BufferSearchBar>,
2798        window: &mut Window,
2799        cx: &mut Context<Self>,
2800    ) -> Div {
2801        let mut registrar = buffer_search::DivRegistrar::new(
2802            |this, _, _cx| match &this.active_view {
2803                ActiveView::PromptEditor {
2804                    buffer_search_bar, ..
2805                } => Some(buffer_search_bar.clone()),
2806                _ => None,
2807            },
2808            cx,
2809        );
2810        BufferSearchBar::register(&mut registrar);
2811        registrar
2812            .into_div()
2813            .size_full()
2814            .relative()
2815            .map(|parent| {
2816                buffer_search_bar.update(cx, |buffer_search_bar, cx| {
2817                    if buffer_search_bar.is_dismissed() {
2818                        return parent;
2819                    }
2820                    parent.child(
2821                        div()
2822                            .p(DynamicSpacing::Base08.rems(cx))
2823                            .border_b_1()
2824                            .border_color(cx.theme().colors().border_variant)
2825                            .bg(cx.theme().colors().editor_background)
2826                            .child(buffer_search_bar.render(window, cx)),
2827                    )
2828                })
2829            })
2830            .child(context_editor.clone())
2831            .child(self.render_drag_target(cx))
2832    }
2833
2834    fn render_drag_target(&self, cx: &Context<Self>) -> Div {
2835        let is_local = self.project.read(cx).is_local();
2836        div()
2837            .invisible()
2838            .absolute()
2839            .top_0()
2840            .right_0()
2841            .bottom_0()
2842            .left_0()
2843            .bg(cx.theme().colors().drop_target_background)
2844            .drag_over::<DraggedTab>(|this, _, _, _| this.visible())
2845            .drag_over::<DraggedSelection>(|this, _, _, _| this.visible())
2846            .when(is_local, |this| {
2847                this.drag_over::<ExternalPaths>(|this, _, _, _| this.visible())
2848            })
2849            .on_drop(cx.listener(move |this, tab: &DraggedTab, window, cx| {
2850                let item = tab.pane.read(cx).item_for_index(tab.ix);
2851                let project_paths = item
2852                    .and_then(|item| item.project_path(cx))
2853                    .into_iter()
2854                    .collect::<Vec<_>>();
2855                this.handle_drop(project_paths, vec![], window, cx);
2856            }))
2857            .on_drop(
2858                cx.listener(move |this, selection: &DraggedSelection, window, cx| {
2859                    let project_paths = selection
2860                        .items()
2861                        .filter_map(|item| this.project.read(cx).path_for_entry(item.entry_id, cx))
2862                        .collect::<Vec<_>>();
2863                    this.handle_drop(project_paths, vec![], window, cx);
2864                }),
2865            )
2866            .on_drop(cx.listener(move |this, paths: &ExternalPaths, window, cx| {
2867                let tasks = paths
2868                    .paths()
2869                    .into_iter()
2870                    .map(|path| {
2871                        Workspace::project_path_for_path(this.project.clone(), &path, false, cx)
2872                    })
2873                    .collect::<Vec<_>>();
2874                cx.spawn_in(window, async move |this, cx| {
2875                    let mut paths = vec![];
2876                    let mut added_worktrees = vec![];
2877                    let opened_paths = futures::future::join_all(tasks).await;
2878                    for entry in opened_paths {
2879                        if let Some((worktree, project_path)) = entry.log_err() {
2880                            added_worktrees.push(worktree);
2881                            paths.push(project_path);
2882                        }
2883                    }
2884                    this.update_in(cx, |this, window, cx| {
2885                        this.handle_drop(paths, added_worktrees, window, cx);
2886                    })
2887                    .ok();
2888                })
2889                .detach();
2890            }))
2891    }
2892
2893    fn handle_drop(
2894        &mut self,
2895        paths: Vec<ProjectPath>,
2896        added_worktrees: Vec<Entity<Worktree>>,
2897        window: &mut Window,
2898        cx: &mut Context<Self>,
2899    ) {
2900        match &self.active_view {
2901            ActiveView::Thread { .. } => {
2902                let context_store = self.thread.read(cx).context_store().clone();
2903                context_store.update(cx, move |context_store, cx| {
2904                    let mut tasks = Vec::new();
2905                    for project_path in &paths {
2906                        tasks.push(context_store.add_file_from_path(
2907                            project_path.clone(),
2908                            false,
2909                            cx,
2910                        ));
2911                    }
2912                    cx.background_spawn(async move {
2913                        futures::future::join_all(tasks).await;
2914                        // Need to hold onto the worktrees until they have already been used when
2915                        // opening the buffers.
2916                        drop(added_worktrees);
2917                    })
2918                    .detach();
2919                });
2920            }
2921            ActiveView::PromptEditor { context_editor, .. } => {
2922                context_editor.update(cx, |context_editor, cx| {
2923                    ContextEditor::insert_dragged_files(
2924                        context_editor,
2925                        paths,
2926                        added_worktrees,
2927                        window,
2928                        cx,
2929                    );
2930                });
2931            }
2932            ActiveView::History | ActiveView::Configuration => {}
2933        }
2934    }
2935
2936    fn create_copy_button(&self, message: impl Into<String>) -> impl IntoElement {
2937        let message = message.into();
2938        IconButton::new("copy", IconName::Copy)
2939            .on_click(move |_, _, cx| {
2940                cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
2941            })
2942            .tooltip(Tooltip::text("Copy Error Message"))
2943    }
2944
2945    fn key_context(&self) -> KeyContext {
2946        let mut key_context = KeyContext::new_with_defaults();
2947        key_context.add("AgentPanel");
2948        if matches!(self.active_view, ActiveView::PromptEditor { .. }) {
2949            key_context.add("prompt_editor");
2950        }
2951        key_context
2952    }
2953}
2954
2955impl Render for AgentPanel {
2956    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2957        // WARNING: Changes to this element hierarchy can have
2958        // non-obvious implications to the layout of children.
2959        //
2960        // If you need to change it, please confirm:
2961        // - The message editor expands (⌘esc) correctly
2962        // - When expanded, the buttons at the bottom of the panel are displayed correctly
2963        // - Font size works as expected and can be changed with ⌘+/⌘-
2964        // - Scrolling in all views works as expected
2965        // - Files can be dropped into the panel
2966        let content = v_flex()
2967            .key_context(self.key_context())
2968            .justify_between()
2969            .size_full()
2970            .on_action(cx.listener(Self::cancel))
2971            .on_action(cx.listener(|this, action: &NewThread, window, cx| {
2972                this.new_thread(action, window, cx);
2973            }))
2974            .on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
2975                this.open_history(window, cx);
2976            }))
2977            .on_action(cx.listener(|this, _: &OpenConfiguration, window, cx| {
2978                this.open_configuration(window, cx);
2979            }))
2980            .on_action(cx.listener(Self::open_active_thread_as_markdown))
2981            .on_action(cx.listener(Self::deploy_rules_library))
2982            .on_action(cx.listener(Self::open_agent_diff))
2983            .on_action(cx.listener(Self::go_back))
2984            .on_action(cx.listener(Self::toggle_navigation_menu))
2985            .on_action(cx.listener(Self::toggle_options_menu))
2986            .on_action(cx.listener(Self::increase_font_size))
2987            .on_action(cx.listener(Self::decrease_font_size))
2988            .on_action(cx.listener(Self::reset_font_size))
2989            .on_action(cx.listener(Self::toggle_zoom))
2990            .child(self.render_toolbar(window, cx))
2991            .children(self.render_upsell(window, cx))
2992            .children(self.render_trial_end_upsell(window, cx))
2993            .map(|parent| match &self.active_view {
2994                ActiveView::Thread { .. } => parent
2995                    .relative()
2996                    .child(self.render_active_thread_or_empty_state(window, cx))
2997                    .children(self.render_tool_use_limit_reached(cx))
2998                    .child(h_flex().child(self.message_editor.clone()))
2999                    .children(self.render_last_error(cx))
3000                    .child(self.render_drag_target(cx)),
3001                ActiveView::History => parent.child(self.history.clone()),
3002                ActiveView::PromptEditor {
3003                    context_editor,
3004                    buffer_search_bar,
3005                    ..
3006                } => parent.child(self.render_prompt_editor(
3007                    context_editor,
3008                    buffer_search_bar,
3009                    window,
3010                    cx,
3011                )),
3012                ActiveView::Configuration => parent.children(self.configuration.clone()),
3013            });
3014
3015        match self.active_view.which_font_size_used() {
3016            WhichFontSize::AgentFont => {
3017                WithRemSize::new(ThemeSettings::get_global(cx).agent_font_size(cx))
3018                    .size_full()
3019                    .child(content)
3020                    .into_any()
3021            }
3022            _ => content.into_any(),
3023        }
3024    }
3025}
3026
3027struct PromptLibraryInlineAssist {
3028    workspace: WeakEntity<Workspace>,
3029}
3030
3031impl PromptLibraryInlineAssist {
3032    pub fn new(workspace: WeakEntity<Workspace>) -> Self {
3033        Self { workspace }
3034    }
3035}
3036
3037impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
3038    fn assist(
3039        &self,
3040        prompt_editor: &Entity<Editor>,
3041        initial_prompt: Option<String>,
3042        window: &mut Window,
3043        cx: &mut Context<RulesLibrary>,
3044    ) {
3045        InlineAssistant::update_global(cx, |assistant, cx| {
3046            let Some(project) = self
3047                .workspace
3048                .upgrade()
3049                .map(|workspace| workspace.read(cx).project().downgrade())
3050            else {
3051                return;
3052            };
3053            let prompt_store = None;
3054            let thread_store = None;
3055            let text_thread_store = None;
3056            let context_store = cx.new(|_| ContextStore::new(project.clone(), None));
3057            assistant.assist(
3058                &prompt_editor,
3059                self.workspace.clone(),
3060                context_store,
3061                project,
3062                prompt_store,
3063                thread_store,
3064                text_thread_store,
3065                initial_prompt,
3066                window,
3067                cx,
3068            )
3069        })
3070    }
3071
3072    fn focus_agent_panel(
3073        &self,
3074        workspace: &mut Workspace,
3075        window: &mut Window,
3076        cx: &mut Context<Workspace>,
3077    ) -> bool {
3078        workspace.focus_panel::<AgentPanel>(window, cx).is_some()
3079    }
3080}
3081
3082pub struct ConcreteAssistantPanelDelegate;
3083
3084impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
3085    fn active_context_editor(
3086        &self,
3087        workspace: &mut Workspace,
3088        _window: &mut Window,
3089        cx: &mut Context<Workspace>,
3090    ) -> Option<Entity<ContextEditor>> {
3091        let panel = workspace.panel::<AgentPanel>(cx)?;
3092        panel.read(cx).active_context_editor()
3093    }
3094
3095    fn open_saved_context(
3096        &self,
3097        workspace: &mut Workspace,
3098        path: Arc<Path>,
3099        window: &mut Window,
3100        cx: &mut Context<Workspace>,
3101    ) -> Task<Result<()>> {
3102        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
3103            return Task::ready(Err(anyhow!("Agent panel not found")));
3104        };
3105
3106        panel.update(cx, |panel, cx| {
3107            panel.open_saved_prompt_editor(path, window, cx)
3108        })
3109    }
3110
3111    fn open_remote_context(
3112        &self,
3113        _workspace: &mut Workspace,
3114        _context_id: assistant_context_editor::ContextId,
3115        _window: &mut Window,
3116        _cx: &mut Context<Workspace>,
3117    ) -> Task<Result<Entity<ContextEditor>>> {
3118        Task::ready(Err(anyhow!("opening remote context not implemented")))
3119    }
3120
3121    fn quote_selection(
3122        &self,
3123        workspace: &mut Workspace,
3124        selection_ranges: Vec<Range<Anchor>>,
3125        buffer: Entity<MultiBuffer>,
3126        window: &mut Window,
3127        cx: &mut Context<Workspace>,
3128    ) {
3129        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
3130            return;
3131        };
3132
3133        if !panel.focus_handle(cx).contains_focused(window, cx) {
3134            workspace.toggle_panel_focus::<AgentPanel>(window, cx);
3135        }
3136
3137        panel.update(cx, |_, cx| {
3138            // Wait to create a new context until the workspace is no longer
3139            // being updated.
3140            cx.defer_in(window, move |panel, window, cx| {
3141                if panel.has_active_thread() {
3142                    panel.message_editor.update(cx, |message_editor, cx| {
3143                        message_editor.context_store().update(cx, |store, cx| {
3144                            let buffer = buffer.read(cx);
3145                            let selection_ranges = selection_ranges
3146                                .into_iter()
3147                                .flat_map(|range| {
3148                                    let (start_buffer, start) =
3149                                        buffer.text_anchor_for_position(range.start, cx)?;
3150                                    let (end_buffer, end) =
3151                                        buffer.text_anchor_for_position(range.end, cx)?;
3152                                    if start_buffer != end_buffer {
3153                                        return None;
3154                                    }
3155                                    Some((start_buffer, start..end))
3156                                })
3157                                .collect::<Vec<_>>();
3158
3159                            for (buffer, range) in selection_ranges {
3160                                store.add_selection(buffer, range, cx);
3161                            }
3162                        })
3163                    })
3164                } else if let Some(context_editor) = panel.active_context_editor() {
3165                    let snapshot = buffer.read(cx).snapshot(cx);
3166                    let selection_ranges = selection_ranges
3167                        .into_iter()
3168                        .map(|range| range.to_point(&snapshot))
3169                        .collect::<Vec<_>>();
3170
3171                    context_editor.update(cx, |context_editor, cx| {
3172                        context_editor.quote_ranges(selection_ranges, snapshot, window, cx)
3173                    });
3174                }
3175            });
3176        });
3177    }
3178}
3179
3180struct Upsell;
3181
3182impl Dismissable for Upsell {
3183    const KEY: &'static str = "dismissed-trial-upsell";
3184}
3185
3186struct TrialEndUpsell;
3187
3188impl Dismissable for TrialEndUpsell {
3189    const KEY: &'static str = "dismissed-trial-end-upsell";
3190}