agent_panel.rs

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