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