agent_panel.rs

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