agent_panel.rs

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