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