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