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