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