agent_panel.rs

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