agent_panel.rs

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