agent_panel.rs

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