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