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