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